Merge branch 'rustdesk:master' into master
This commit is contained in:
commit
e09b4f878e
2
.github/workflows/flutter-build.yml
vendored
2
.github/workflows/flutter-build.yml
vendored
@ -22,7 +22,7 @@ env:
|
||||
# vcpkg version: 2023.04.15
|
||||
# for multiarch gcc compatibility
|
||||
VCPKG_COMMIT_ID: "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1"
|
||||
VERSION: "1.2.2"
|
||||
VERSION: "1.2.3"
|
||||
NDK_VERSION: "r25c"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: '${{ secrets.ANDROID_SIGNING_KEY }}'
|
||||
|
2
.github/workflows/flutter-tag.yml
vendored
2
.github/workflows/flutter-tag.yml
vendored
@ -15,4 +15,4 @@ jobs:
|
||||
secrets: inherit
|
||||
with:
|
||||
upload-artifact: true
|
||||
upload-tag: "1.2.2"
|
||||
upload-tag: "1.2.3"
|
||||
|
2
.github/workflows/history.yml
vendored
2
.github/workflows/history.yml
vendored
@ -10,7 +10,7 @@ env:
|
||||
# vcpkg version: 2022.05.10
|
||||
# for multiarch gcc compatibility
|
||||
VCPKG_COMMIT_ID: "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1"
|
||||
VERSION: "1.2.2"
|
||||
VERSION: "1.2.3"
|
||||
|
||||
jobs:
|
||||
build-for-history-windows:
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -5124,7 +5124,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk"
|
||||
version = "1.2.2"
|
||||
version = "1.2.3"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"arboard",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.2.2"
|
||||
version = "1.2.3"
|
||||
authors = ["rustdesk <info@rustdesk.com>"]
|
||||
edition = "2021"
|
||||
build= "build.rs"
|
||||
|
@ -2,7 +2,7 @@
|
||||
version: 1
|
||||
script:
|
||||
- rm -rf ./AppDir || true
|
||||
- bsdtar -zxvf ../rustdesk-1.2.2.deb
|
||||
- bsdtar -zxvf ../rustdesk-1.2.3.deb
|
||||
- tar -xvf ./data.tar.xz
|
||||
- mkdir ./AppDir
|
||||
- mv ./usr ./AppDir/usr
|
||||
@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.2.2
|
||||
version: 1.2.3
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
@ -2,7 +2,7 @@
|
||||
version: 1
|
||||
script:
|
||||
- rm -rf ./AppDir || true
|
||||
- bsdtar -zxvf ../rustdesk-1.2.2.deb
|
||||
- bsdtar -zxvf ../rustdesk-1.2.3.deb
|
||||
- tar -xvf ./data.tar.xz
|
||||
- mkdir ./AppDir
|
||||
- mv ./usr ./AppDir/usr
|
||||
@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.2.2
|
||||
version: 1.2.3
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
@ -12,7 +12,7 @@
|
||||
"name": "rustdesk",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"bsdtar -zxvf rustdesk-1.2.2.deb",
|
||||
"bsdtar -zxvf rustdesk-1.2.3.deb",
|
||||
"tar -xvf ./data.tar.xz",
|
||||
"cp -r ./usr/* /app/",
|
||||
"mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk",
|
||||
@ -26,7 +26,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"path": "../rustdesk-1.2.2.deb"
|
||||
"path": "../rustdesk-1.2.3.deb"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
|
@ -26,6 +26,13 @@ const val WHEEL_BUTTON_UP = 34
|
||||
const val WHEEL_DOWN = 523331
|
||||
const val WHEEL_UP = 963
|
||||
|
||||
const val TOUCH_SCALE_START = 1
|
||||
const val TOUCH_SCALE = 2
|
||||
const val TOUCH_SCALE_END = 3
|
||||
const val TOUCH_PAN_START = 4
|
||||
const val TOUCH_PAN_UPDATE = 5
|
||||
const val TOUCH_PAN_END = 6
|
||||
|
||||
const val WHEEL_STEP = 120
|
||||
const val WHEEL_DURATION = 50L
|
||||
const val LONG_TAP_DELAY = 200L
|
||||
@ -167,6 +174,30 @@ class InputService : AccessibilityService() {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun onTouchInput(mask: Int, _x: Int, _y: Int) {
|
||||
when (mask) {
|
||||
TOUCH_PAN_UPDATE -> {
|
||||
mouseX -= _x * SCREEN_INFO.scale
|
||||
mouseY -= _y * SCREEN_INFO.scale
|
||||
mouseX = max(0, mouseX);
|
||||
mouseY = max(0, mouseY);
|
||||
continueGesture(mouseX, mouseY)
|
||||
}
|
||||
TOUCH_PAN_START -> {
|
||||
mouseX = max(0, _x) * SCREEN_INFO.scale
|
||||
mouseY = max(0, _y) * SCREEN_INFO.scale
|
||||
startGesture(mouseX, mouseY)
|
||||
}
|
||||
TOUCH_PAN_END -> {
|
||||
endGesture(mouseX, mouseY)
|
||||
mouseX = max(0, _x) * SCREEN_INFO.scale
|
||||
mouseY = max(0, _y) * SCREEN_INFO.scale
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
private fun consumeWheelActions() {
|
||||
if (isWheelActionsPolling) {
|
||||
|
@ -71,17 +71,26 @@ class MainService : Service() {
|
||||
|
||||
@Keep
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun rustMouseInput(mask: Int, x: Int, y: Int) {
|
||||
fun rustPointerInput(kind: String, mask: Int, x: Int, y: Int) {
|
||||
// turn on screen with LIFT_DOWN when screen off
|
||||
if (!powerManager.isInteractive && mask == LIFT_DOWN) {
|
||||
if (!powerManager.isInteractive && (kind == "touch" || mask == LIFT_DOWN)) {
|
||||
if (wakeLock.isHeld) {
|
||||
Log.d(logTag,"Turn on Screen, WakeLock release")
|
||||
Log.d(logTag, "Turn on Screen, WakeLock release")
|
||||
wakeLock.release()
|
||||
}
|
||||
Log.d(logTag,"Turn on Screen")
|
||||
wakeLock.acquire(5000)
|
||||
} else {
|
||||
InputService.ctx?.onMouseInput(mask,x,y)
|
||||
when (kind) {
|
||||
"touch" -> {
|
||||
InputService.ctx?.onTouchInput(mask, x, y)
|
||||
}
|
||||
"mouse" -> {
|
||||
InputService.ctx?.onMouseInput(mask, x, y)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
1
flutter/assets/auth-gitlab.svg
Normal file
1
flutter/assets/auth-gitlab.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="236" preserveAspectRatio="xMidYMid"><path fill="#e24329" d="m128.075 236.075 47.104-144.97H80.97z"/><path fill="#fc6d26" d="M128.075 236.074 80.97 91.104H14.956z"/><path fill="#fca326" d="M14.956 91.104.642 135.16a9.752 9.752 0 0 0 3.542 10.903l123.891 90.012z"/><path fill="#e24329" d="M14.956 91.105H80.97L52.601 3.79c-1.46-4.493-7.816-4.492-9.275 0z"/><path fill="#fc6d26" d="m128.075 236.074 47.104-144.97h66.015z"/><path fill="#fca326" d="m241.194 91.104 14.314 44.056a9.752 9.752 0 0 1-3.543 10.903l-123.89 90.012z"/><path fill="#e24329" d="M241.194 91.105h-66.015l28.37-87.315c1.46-4.493 7.816-4.492 9.275 0z"/></svg>
|
After Width: | Height: | Size: 684 B |
@ -1077,7 +1077,7 @@ Color str2color(String str, [alpha = 0xFF]) {
|
||||
return Color((hash & 0xFF7FFF) | (alpha << 24));
|
||||
}
|
||||
|
||||
Color str2color2(String str, [alpha = 0xFF]) {
|
||||
Color str2color2(String str, {List<int> existing = const []}) {
|
||||
Map<String, Color> colorMap = {
|
||||
"red": Colors.red,
|
||||
"green": Colors.green,
|
||||
@ -1094,10 +1094,10 @@ Color str2color2(String str, [alpha = 0xFF]) {
|
||||
};
|
||||
final color = colorMap[str.toLowerCase()];
|
||||
if (color != null) {
|
||||
return color.withAlpha(alpha);
|
||||
return color.withAlpha(0xFF);
|
||||
}
|
||||
if (str.toLowerCase() == 'yellow') {
|
||||
return Colors.yellow.withAlpha(alpha);
|
||||
return Colors.yellow.withAlpha(0xFF);
|
||||
}
|
||||
var hash = 0;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
@ -1105,7 +1105,15 @@ Color str2color2(String str, [alpha = 0xFF]) {
|
||||
}
|
||||
List<Color> colorList = colorMap.values.toList();
|
||||
hash = hash % colorList.length;
|
||||
return colorList[hash].withAlpha(alpha);
|
||||
var result = colorList[hash].withAlpha(0xFF);
|
||||
if (existing.contains(result.value)) {
|
||||
Color? notUsed =
|
||||
colorList.firstWhereOrNull((e) => !existing.contains(e.value));
|
||||
if (notUsed != null) {
|
||||
result = notUsed;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const K = 1024;
|
||||
|
@ -7,6 +7,7 @@ import 'package:flutter_hbb/models/ab_model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flex_color_picker/flex_color_picker.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import 'dialog.dart';
|
||||
@ -513,7 +514,7 @@ class AddressBookTag extends StatelessWidget {
|
||||
child: Obx(() => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: tags.contains(name)
|
||||
? str2color2(name, 0xFF)
|
||||
? gFFI.abModel.getTagColor(name)
|
||||
: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(4)),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 4.0),
|
||||
@ -528,7 +529,7 @@ class AddressBookTag extends StatelessWidget {
|
||||
shape: BoxShape.circle,
|
||||
color: tags.contains(name)
|
||||
? Colors.white
|
||||
: str2color2(name)),
|
||||
: gFFI.abModel.getTagColor(name)),
|
||||
).marginOnly(right: radius / 2),
|
||||
Expanded(
|
||||
child: Text(name,
|
||||
@ -568,6 +569,30 @@ class AddressBookTag extends StatelessWidget {
|
||||
Future.delayed(Duration.zero, () => Get.back());
|
||||
});
|
||||
}),
|
||||
getEntry(translate(translate('Change Color')), () async {
|
||||
final model = gFFI.abModel;
|
||||
Color oldColor = model.getTagColor(name);
|
||||
Color newColor = await showColorPickerDialog(
|
||||
context,
|
||||
oldColor,
|
||||
pickersEnabled: {
|
||||
ColorPickerType.accent: false,
|
||||
ColorPickerType.wheel: true,
|
||||
},
|
||||
pickerTypeLabels: {
|
||||
ColorPickerType.primary: translate("Primary Color"),
|
||||
ColorPickerType.wheel: translate("HSV Color"),
|
||||
},
|
||||
actionButtons: ColorPickerActionButtons(
|
||||
dialogOkButtonLabel: translate("OK"),
|
||||
dialogCancelButtonLabel: translate("Cancel")),
|
||||
showColorCode: true,
|
||||
);
|
||||
if (oldColor != newColor) {
|
||||
model.setTagColor(name, newColor);
|
||||
model.pushAb();
|
||||
}
|
||||
}),
|
||||
getEntry(translate("Delete"), () {
|
||||
gFFI.abModel.deleteTag(name);
|
||||
gFFI.abModel.pushAb();
|
||||
|
@ -14,6 +14,7 @@ import './dialog.dart';
|
||||
|
||||
const kOpSvgList = [
|
||||
'github',
|
||||
'gitlab',
|
||||
'google',
|
||||
'apple',
|
||||
'okta',
|
||||
@ -72,6 +73,11 @@ class ButtonOP extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final opLabel = {
|
||||
'github': 'GitHub',
|
||||
'gitlab': 'GitLab'
|
||||
}[op.toLowerCase()] ??
|
||||
toCapitalized(op);
|
||||
return Row(children: [
|
||||
Container(
|
||||
height: height,
|
||||
@ -97,8 +103,7 @@ class ButtonOP extends StatelessWidget {
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${translate("Continue with")} ${op.toLowerCase() == "github" ? "GitHub" : toCapitalized(op)}')),
|
||||
child: Text('${translate("Continue with")} $opLabel')),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -63,58 +63,29 @@ class _PeerCardState extends State<_PeerCard>
|
||||
|
||||
Widget _buildMobile() {
|
||||
final peer = super.widget.peer;
|
||||
final name =
|
||||
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
|
||||
final PeerTabModel peerTabModel = Provider.of(context);
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 2),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (peerTabModel.multiSelectionMode) {
|
||||
peerTabModel.select(peer);
|
||||
} else {
|
||||
if (!isWebDesktop) {
|
||||
connectInPeerTab(context, peer.id, widget.tab);
|
||||
}
|
||||
}
|
||||
},
|
||||
onDoubleTap: isWebDesktop
|
||||
? () => connectInPeerTab(context, peer.id, widget.tab)
|
||||
: null,
|
||||
onLongPress: () {
|
||||
onTap: () {
|
||||
if (peerTabModel.multiSelectionMode) {
|
||||
peerTabModel.select(peer);
|
||||
},
|
||||
child: Container(
|
||||
} else {
|
||||
if (!isWebDesktop) {
|
||||
connectInPeerTab(context, peer.id, widget.tab);
|
||||
}
|
||||
}
|
||||
},
|
||||
onDoubleTap: isWebDesktop
|
||||
? () => connectInPeerTab(context, peer.id, widget.tab)
|
||||
: null,
|
||||
onLongPress: () {
|
||||
peerTabModel.select(peer);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 12, top: 8, bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: str2color('${peer.id}${peer.platform}', 0x7f),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: getPlatformImage(peer.platform)),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
getOnline(4, peer.online),
|
||||
Text(peer.alias.isEmpty
|
||||
? formatID(peer.id)
|
||||
: peer.alias)
|
||||
]),
|
||||
Text(name)
|
||||
],
|
||||
).paddingOnly(left: 8.0),
|
||||
),
|
||||
checkBoxOrActionMoreMobile(peer),
|
||||
],
|
||||
),
|
||||
)));
|
||||
child: _buildPeerTile(context, peer, null)),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildDesktop() {
|
||||
@ -161,87 +132,96 @@ class _PeerCardState extends State<_PeerCard>
|
||||
}
|
||||
|
||||
Widget _buildPeerTile(
|
||||
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
|
||||
BuildContext context, Peer peer, Rx<BoxDecoration?>? deco) {
|
||||
final name =
|
||||
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
|
||||
final greyStyle = TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
|
||||
final child = Obx(
|
||||
() => Container(
|
||||
foregroundDecoration: deco.value,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: str2color('${peer.id}${peer.platform}', 0x7f),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(_tileRadius),
|
||||
bottomLeft: Radius.circular(_tileRadius),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
width: 42,
|
||||
child: getPlatformImage(peer.platform, size: 30).paddingAll(6),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(_tileRadius),
|
||||
bottomRight: Radius.circular(_tileRadius),
|
||||
final child = Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: str2color('${peer.id}${peer.platform}', 0x7f),
|
||||
borderRadius: isMobile
|
||||
? BorderRadius.circular(_tileRadius)
|
||||
: BorderRadius.only(
|
||||
topLeft: Radius.circular(_tileRadius),
|
||||
bottomLeft: Radius.circular(_tileRadius),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(children: [
|
||||
getOnline(8, peer.online),
|
||||
Expanded(
|
||||
child: Text(
|
||||
peer.alias.isEmpty
|
||||
? formatID(peer.id)
|
||||
: peer.alias,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
)),
|
||||
]).marginOnly(bottom: 0, top: 2),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
name,
|
||||
style: greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
).marginOnly(top: 2),
|
||||
),
|
||||
checkBoxOrActionMoreDesktop(peer, isTile: true),
|
||||
],
|
||||
).paddingOnly(left: 10.0, top: 3.0),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
width: isMobile ? 50 : 42,
|
||||
height: isMobile ? 50 : null,
|
||||
child: getPlatformImage(peer.platform, size: isMobile ? 38 : 30)
|
||||
.paddingAll(6),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(_tileRadius),
|
||||
bottomRight: Radius.circular(_tileRadius),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(children: [
|
||||
getOnline(isMobile ? 4 : 8, peer.online),
|
||||
Expanded(
|
||||
child: Text(
|
||||
peer.alias.isEmpty ? formatID(peer.id) : peer.alias,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
)),
|
||||
]).marginOnly(top: isMobile ? 0 : 2),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
name,
|
||||
style: isMobile ? null : greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
).marginOnly(top: 2),
|
||||
),
|
||||
isMobile
|
||||
? checkBoxOrActionMoreMobile(peer)
|
||||
: checkBoxOrActionMoreDesktop(peer, isTile: true),
|
||||
],
|
||||
).paddingOnly(left: 10.0, top: 3.0),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
final colors = _frontN(peer.tags, 25).map((e) => str2color2(e)).toList();
|
||||
final colors =
|
||||
_frontN(peer.tags, 25).map((e) => gFFI.abModel.getTagColor(e)).toList();
|
||||
return Tooltip(
|
||||
message: peer.tags.isNotEmpty
|
||||
? '${translate('Tags')}: ${peer.tags.join(', ')}'
|
||||
: '',
|
||||
message: isMobile
|
||||
? ''
|
||||
: peer.tags.isNotEmpty
|
||||
? '${translate('Tags')}: ${peer.tags.join(', ')}'
|
||||
: '',
|
||||
child: Stack(children: [
|
||||
child,
|
||||
deco == null
|
||||
? child
|
||||
: Obx(
|
||||
() => Container(
|
||||
foregroundDecoration: deco.value,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
if (colors.isNotEmpty)
|
||||
Positioned(
|
||||
top: 2,
|
||||
right: 10,
|
||||
right: isMobile ? 20 : 10,
|
||||
child: CustomPaint(
|
||||
painter: TagPainter(radius: 3, colors: colors),
|
||||
),
|
||||
@ -332,7 +312,8 @@ class _PeerCardState extends State<_PeerCard>
|
||||
),
|
||||
);
|
||||
|
||||
final colors = _frontN(peer.tags, 25).map((e) => str2color2(e)).toList();
|
||||
final colors =
|
||||
_frontN(peer.tags, 25).map((e) => gFFI.abModel.getTagColor(e)).toList();
|
||||
return Tooltip(
|
||||
message: peer.tags.isNotEmpty
|
||||
? '${translate('Tags')}: ${peer.tags.join(', ')}'
|
||||
@ -754,11 +735,12 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
proc: () async {
|
||||
bool result = gFFI.abModel.changePassword(id, '');
|
||||
await bind.mainForgetPassword(id: id);
|
||||
bool toast = false;
|
||||
if (result) {
|
||||
bool toast = tab == PeerTabIndex.ab;
|
||||
toast = tab == PeerTabIndex.ab;
|
||||
gFFI.abModel.pushAb(toastIfFail: toast, toastIfSucc: toast);
|
||||
}
|
||||
showToast(translate('Successful'));
|
||||
if (!toast) showToast(translate('Successful'));
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
|
@ -426,7 +426,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
Widget selectionCount(int count) {
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: Text('$count selected'),
|
||||
child: Text('$count ${translate('Selected')}'),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -421,15 +421,12 @@ class AddressBookPeersView extends BasePeersView {
|
||||
if (selectedTags.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
if (idents.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
for (final tag in selectedTags) {
|
||||
if (!idents.contains(tag)) {
|
||||
return false;
|
||||
if (idents.contains(tag)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import 'package:flutter/gestures.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_hbb/models/input_model.dart';
|
||||
|
||||
@ -263,9 +264,9 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (scale != 0) {
|
||||
bind.sessionSendPointer(
|
||||
sessionId: sessionId,
|
||||
msg: json.encode({
|
||||
'touch': {'scale': scale}
|
||||
}));
|
||||
msg: json.encode(
|
||||
PointerEventToRust(kPointerEventKindTouch, 'scale', scale)
|
||||
.toJson()));
|
||||
}
|
||||
} else {
|
||||
// mobile
|
||||
@ -283,9 +284,8 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (isDesktop) {
|
||||
bind.sessionSendPointer(
|
||||
sessionId: sessionId,
|
||||
msg: json.encode({
|
||||
'touch': {'scale': 0}
|
||||
}));
|
||||
msg: json.encode(
|
||||
PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson()));
|
||||
} else {
|
||||
// mobile
|
||||
_scale = 1;
|
||||
|
@ -5,6 +5,7 @@ import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
|
||||
const double kDesktopRemoteTabBarHeight = 28.0;
|
||||
const int kInvalidWindowId = -1;
|
||||
const int kMainWindowId = 0;
|
||||
|
||||
const String kPeerPlatformWindows = "Windows";
|
||||
@ -38,7 +39,7 @@ const String kWindowEventGetRemoteList = "get_remote_list";
|
||||
const String kWindowEventGetSessionIdList = "get_session_id_list";
|
||||
|
||||
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
|
||||
const String kWindowEventCloseForSeparateWindow = "close_for_separate_window";
|
||||
const String kWindowEventGetCachedSessionData = "get_cached_session_data";
|
||||
|
||||
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
|
||||
const String kOptionOpenInTabs = "allow-open-in-tabs";
|
||||
@ -54,6 +55,9 @@ const String kTabLabelSettingPage = "Settings";
|
||||
const String kWindowPrefix = "wm_";
|
||||
const int kWindowMainId = 0;
|
||||
|
||||
const String kPointerEventKindTouch = "touch";
|
||||
const String kPointerEventKindMouse = "mouse";
|
||||
|
||||
// the executable name of the portable version
|
||||
const String kEnvPortableExecutable = "RUSTDESK_APPNAME";
|
||||
|
||||
|
@ -17,7 +17,6 @@ import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../../common/widgets/dialog.dart';
|
||||
import '../../common/widgets/login.dart';
|
||||
|
@ -35,6 +35,7 @@ class RemotePage extends StatefulWidget {
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.sessionId,
|
||||
required this.tabWindowId,
|
||||
required this.password,
|
||||
required this.toolbarState,
|
||||
required this.tabController,
|
||||
@ -44,6 +45,7 @@ class RemotePage extends StatefulWidget {
|
||||
|
||||
final String id;
|
||||
final SessionID? sessionId;
|
||||
final int? tabWindowId;
|
||||
final String? password;
|
||||
final ToolbarState toolbarState;
|
||||
final String? switchUuid;
|
||||
@ -106,6 +108,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
password: widget.password,
|
||||
switchUuid: widget.switchUuid,
|
||||
forceRelay: widget.forceRelay,
|
||||
tabWindowId: widget.tabWindowId,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||
|
@ -55,6 +55,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
RemoteCountState.init();
|
||||
peerId = params['id'];
|
||||
final sessionId = params['session_id'];
|
||||
final tabWindowId = params['tab_window_id'];
|
||||
if (peerId != null) {
|
||||
ConnectionTypeState.init(peerId!);
|
||||
tabController.onSelected = (id) {
|
||||
@ -77,6 +78,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
key: ValueKey(peerId),
|
||||
id: peerId!,
|
||||
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||
tabWindowId: tabWindowId,
|
||||
password: params['password'],
|
||||
toolbarState: _toolbarState,
|
||||
tabController: tabController,
|
||||
@ -98,12 +100,14 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
print(
|
||||
"[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
||||
|
||||
dynamic returnValue;
|
||||
// for simplify, just replace connectionId
|
||||
if (call.method == kWindowEventNewRemoteDesktop) {
|
||||
final args = jsonDecode(call.arguments);
|
||||
final id = args['id'];
|
||||
final switchUuid = args['switch_uuid'];
|
||||
final sessionId = args['session_id'];
|
||||
final tabWindowId = args['tab_window_id'];
|
||||
windowOnTop(windowId());
|
||||
ConnectionTypeState.init(id);
|
||||
_toolbarState.setShow(
|
||||
@ -118,6 +122,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
key: ValueKey(id),
|
||||
id: id,
|
||||
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||
tabWindowId: tabWindowId,
|
||||
password: args['password'],
|
||||
toolbarState: _toolbarState,
|
||||
tabController: tabController,
|
||||
@ -147,12 +152,24 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
.map((e) => '${e.key},${(e.page as RemotePage).ffi.sessionId}')
|
||||
.toList()
|
||||
.join(';');
|
||||
} else if (call.method == kWindowEventCloseForSeparateWindow) {
|
||||
} else if (call.method == kWindowEventGetCachedSessionData) {
|
||||
// Ready to show new window and close old tab.
|
||||
final peerId = call.arguments;
|
||||
closeSessionOnDispose[peerId] = false;
|
||||
tabController.closeBy(peerId);
|
||||
try {
|
||||
final remotePage = tabController.state.value.tabs
|
||||
.firstWhere((tab) => tab.key == peerId)
|
||||
.page as RemotePage;
|
||||
returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString();
|
||||
} catch (e) {
|
||||
debugPrint('Failed to get cached session data: $e');
|
||||
}
|
||||
if (returnValue != null) {
|
||||
closeSessionOnDispose[peerId] = false;
|
||||
tabController.closeBy(peerId);
|
||||
}
|
||||
}
|
||||
_update_remote_count();
|
||||
return returnValue;
|
||||
});
|
||||
Future.delayed(Duration.zero, () {
|
||||
restoreWindowPosition(
|
||||
@ -337,7 +354,15 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
));
|
||||
}
|
||||
|
||||
if (perms['keyboard'] != false && !ffi.ffiModel.viewOnly) {}
|
||||
if (perms['keyboard'] != false && !ffi.ffiModel.viewOnly) {
|
||||
menu.add(RemoteMenuEntry.insertLock(sessionId, padding,
|
||||
dismissFunc: cancelFunc));
|
||||
|
||||
if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) {
|
||||
menu.add(RemoteMenuEntry.insertCtrlAltDel(sessionId, padding,
|
||||
dismissFunc: cancelFunc));
|
||||
}
|
||||
}
|
||||
|
||||
menu.addAll([
|
||||
MenuEntryDivider<String>(),
|
||||
|
@ -771,7 +771,7 @@ class ScreenAdjustor {
|
||||
updateScreen() async {
|
||||
final v = await rustDeskWinManager.call(
|
||||
WindowType.Main, kWindowGetWindowInfo, '');
|
||||
final String valueStr = v;
|
||||
final String valueStr = v.result;
|
||||
if (valueStr.isEmpty) {
|
||||
_screen = null;
|
||||
} else {
|
||||
|
@ -28,6 +28,7 @@ class AbModel {
|
||||
final pullError = "".obs;
|
||||
final pushError = "".obs;
|
||||
final tags = [].obs;
|
||||
final RxMap<String, int> tagColors = Map<String, int>.fromEntries([]).obs;
|
||||
final peers = List<Peer>.empty(growable: true).obs;
|
||||
final sortTags = shouldSortTags().obs;
|
||||
final retrying = false.obs;
|
||||
@ -80,10 +81,11 @@ class AbModel {
|
||||
if (resp.body.toLowerCase() == "null") {
|
||||
// normal reply, emtpy ab return null
|
||||
tags.clear();
|
||||
tagColors.clear();
|
||||
peers.clear();
|
||||
} else if (resp.body.isNotEmpty) {
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecode(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
} else if (json.containsKey('data')) {
|
||||
@ -93,26 +95,7 @@ class AbModel {
|
||||
} catch (e) {}
|
||||
final data = jsonDecode(json['data']);
|
||||
if (data != null) {
|
||||
final oldOnlineIDs =
|
||||
peers.where((e) => e.online).map((e) => e.id).toList();
|
||||
tags.clear();
|
||||
peers.clear();
|
||||
if (data['tags'] is List) {
|
||||
tags.value = data['tags'];
|
||||
}
|
||||
if (data['peers'] is List) {
|
||||
for (final peer in data['peers']) {
|
||||
peers.add(Peer.fromJson(peer));
|
||||
}
|
||||
}
|
||||
if (isFull(false)) {
|
||||
peers.removeRange(licensedDevices, peers.length);
|
||||
}
|
||||
// restore online
|
||||
peers
|
||||
.where((e) => oldOnlineIDs.contains(e.id))
|
||||
.map((e) => e.online = true)
|
||||
.toList();
|
||||
_deserialize(data);
|
||||
_saveCache(); // save on success
|
||||
}
|
||||
}
|
||||
@ -242,10 +225,7 @@ class AbModel {
|
||||
final api = "${await bind.mainGetApiServer()}/api/ab";
|
||||
var authHeaders = getHttpHeaders();
|
||||
authHeaders['Content-Type'] = "application/json";
|
||||
final peersJsonData = peers.map((e) => e.toAbUploadJson()).toList();
|
||||
final body = jsonEncode({
|
||||
"data": jsonEncode({"tags": tags, "peers": peersJsonData})
|
||||
});
|
||||
final body = jsonEncode({"data": jsonEncode(_serialize())});
|
||||
http.Response resp;
|
||||
// support compression
|
||||
if (licensedDevices > 0 && body.length > 1024) {
|
||||
@ -261,7 +241,7 @@ class AbModel {
|
||||
ret = true;
|
||||
_saveCache();
|
||||
} else {
|
||||
Map<String, dynamic> json = _jsonDecode(resp.body, resp.statusCode);
|
||||
Map<String, dynamic> json = _jsonDecodeResp(resp.body, resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
} else if (resp.statusCode == 200) {
|
||||
@ -318,6 +298,7 @@ class AbModel {
|
||||
void deleteTag(String tag) {
|
||||
gFFI.abModel.selectedTags.remove(tag);
|
||||
tags.removeWhere((element) => element == tag);
|
||||
tagColors.remove(tag);
|
||||
for (var peer in peers) {
|
||||
if (peer.tags.isEmpty) {
|
||||
continue;
|
||||
@ -353,6 +334,11 @@ class AbModel {
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
int? oldColor = tagColors[oldTag];
|
||||
if (oldColor != null) {
|
||||
tagColors.remove(oldTag);
|
||||
tagColors.addAll({newTag: oldColor});
|
||||
}
|
||||
}
|
||||
|
||||
void unsetSelectedTags() {
|
||||
@ -368,6 +354,20 @@ class AbModel {
|
||||
}
|
||||
}
|
||||
|
||||
Color getTagColor(String tag) {
|
||||
int? colorValue = tagColors[tag];
|
||||
if (colorValue != null) {
|
||||
return Color(colorValue);
|
||||
}
|
||||
return str2color2(tag, existing: tagColors.values.toList());
|
||||
}
|
||||
|
||||
setTagColor(String tag, Color color) {
|
||||
if (tags.contains(tag)) {
|
||||
tagColors[tag] = color.value;
|
||||
}
|
||||
}
|
||||
|
||||
void merge(Peer r, Peer p) {
|
||||
p.hash = r.hash.isEmpty ? p.hash : r.hash;
|
||||
p.username = r.username.isEmpty ? p.username : r.username;
|
||||
@ -467,12 +467,10 @@ class AbModel {
|
||||
|
||||
_saveCache() {
|
||||
try {
|
||||
final peersJsonData = peers.map((e) => e.toAbUploadJson()).toList();
|
||||
final m = <String, dynamic>{
|
||||
var m = _serialize();
|
||||
m.addAll(<String, dynamic>{
|
||||
"access_token": bind.mainGetLocalOption(key: 'access_token'),
|
||||
"peers": peersJsonData,
|
||||
"tags": tags.map((e) => e.toString()).toList(),
|
||||
};
|
||||
});
|
||||
bind.mainSaveAb(json: jsonEncode(m));
|
||||
} catch (e) {
|
||||
debugPrint('ab save:$e');
|
||||
@ -488,22 +486,13 @@ class AbModel {
|
||||
final cache = await bind.mainLoadAb();
|
||||
final data = jsonDecode(cache);
|
||||
if (data == null || data['access_token'] != access_token) return;
|
||||
tags.clear();
|
||||
peers.clear();
|
||||
if (data['tags'] is List) {
|
||||
tags.value = data['tags'];
|
||||
}
|
||||
if (data['peers'] is List) {
|
||||
for (final peer in data['peers']) {
|
||||
peers.add(Peer.fromJson(peer));
|
||||
}
|
||||
}
|
||||
_deserialize(data);
|
||||
} catch (e) {
|
||||
debugPrint("load ab cache: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _jsonDecode(String body, int statusCode) {
|
||||
Map<String, dynamic> _jsonDecodeResp(String body, int statusCode) {
|
||||
try {
|
||||
Map<String, dynamic> json = jsonDecode(body);
|
||||
return json;
|
||||
@ -516,6 +505,50 @@ class AbModel {
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _serialize() {
|
||||
final peersJsonData = peers.map((e) => e.toAbUploadJson()).toList();
|
||||
final tagColorJsonData = jsonEncode(tagColors);
|
||||
return {
|
||||
"tags": tags,
|
||||
"peers": peersJsonData,
|
||||
"tag_colors": tagColorJsonData
|
||||
};
|
||||
}
|
||||
|
||||
_deserialize(dynamic data) {
|
||||
if (data == null) return;
|
||||
final oldOnlineIDs = peers.where((e) => e.online).map((e) => e.id).toList();
|
||||
tags.clear();
|
||||
tagColors.clear();
|
||||
peers.clear();
|
||||
if (data['tags'] is List) {
|
||||
tags.value = data['tags'];
|
||||
}
|
||||
if (data['peers'] is List) {
|
||||
for (final peer in data['peers']) {
|
||||
peers.add(Peer.fromJson(peer));
|
||||
}
|
||||
}
|
||||
if (isFull(false)) {
|
||||
peers.removeRange(licensedDevices, peers.length);
|
||||
}
|
||||
// restore online
|
||||
peers
|
||||
.where((e) => oldOnlineIDs.contains(e.id))
|
||||
.map((e) => e.online = true)
|
||||
.toList();
|
||||
if (data['tag_colors'] is String) {
|
||||
Map<String, dynamic> map = jsonDecode(data['tag_colors']);
|
||||
tagColors.value = Map<String, int>.from(map);
|
||||
}
|
||||
// add color to tag
|
||||
final tagsWithoutColor =
|
||||
tags.toList().where((e) => !tagColors.containsKey(e)).toList();
|
||||
for (var t in tagsWithoutColor) {
|
||||
tagColors[t] = str2color2(t, existing: tagColors.values.toList()).value;
|
||||
}
|
||||
}
|
||||
|
||||
reSyncToast(Future<bool> future) {
|
||||
if (!shouldSyncAb()) return;
|
||||
Future.delayed(Duration.zero, () async {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart';
|
||||
|
||||
@ -21,7 +20,6 @@ class RenderTexture {
|
||||
_sessionId = sessionId;
|
||||
|
||||
textureRenderer.createTexture(_textureKey).then((id) async {
|
||||
debugPrint("id: $id, texture_key: $_textureKey");
|
||||
if (id != -1) {
|
||||
final ptr = await textureRenderer.getTexturePtr(_textureKey);
|
||||
platformFFI.registerTexture(sessionId, ptr);
|
||||
|
@ -35,6 +35,24 @@ extension ToString on MouseButtons {
|
||||
}
|
||||
}
|
||||
|
||||
class PointerEventToRust {
|
||||
final String kind;
|
||||
final String type;
|
||||
final dynamic value;
|
||||
|
||||
PointerEventToRust(this.kind, this.type, this.value);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'k': kind,
|
||||
'v': {
|
||||
't': type,
|
||||
'v': value,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class InputModel {
|
||||
final WeakReference<FFI> parent;
|
||||
String keyboardMode = "legacy";
|
||||
@ -62,11 +80,11 @@ class InputModel {
|
||||
int _lastButtons = 0;
|
||||
Offset lastMousePos = Offset.zero;
|
||||
|
||||
get id => parent.target?.id ?? "";
|
||||
|
||||
late final SessionID sessionId;
|
||||
|
||||
bool get keyboardPerm => parent.target!.ffiModel.keyboard;
|
||||
String get id => parent.target?.id ?? '';
|
||||
String? get peerPlatform => parent.target?.ffiModel.pi.platform;
|
||||
|
||||
InputModel(this.parent) {
|
||||
sessionId = parent.target!.sessionId;
|
||||
@ -223,14 +241,8 @@ class InputModel {
|
||||
command: command);
|
||||
}
|
||||
|
||||
Map<String, dynamic> getEvent(PointerEvent evt, String type) {
|
||||
Map<String, dynamic> _getMouseEvent(PointerEvent evt, String type) {
|
||||
final Map<String, dynamic> out = {};
|
||||
out['x'] = evt.position.dx;
|
||||
out['y'] = evt.position.dy;
|
||||
if (alt) out['alt'] = 'true';
|
||||
if (shift) out['shift'] = 'true';
|
||||
if (ctrl) out['ctrl'] = 'true';
|
||||
if (command) out['command'] = 'true';
|
||||
|
||||
// Check update event type and set buttons to be sent.
|
||||
int buttons = _lastButtons;
|
||||
@ -260,7 +272,6 @@ class InputModel {
|
||||
|
||||
out['buttons'] = buttons;
|
||||
out['type'] = type;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -292,7 +303,7 @@ class InputModel {
|
||||
}
|
||||
|
||||
/// Modify the given modifier map [evt] based on current modifier key status.
|
||||
Map<String, String> modify(Map<String, String> evt) {
|
||||
Map<String, dynamic> modify(Map<String, dynamic> evt) {
|
||||
if (ctrl) evt['ctrl'] = 'true';
|
||||
if (shift) evt['shift'] = 'true';
|
||||
if (alt) evt['alt'] = 'true';
|
||||
@ -334,27 +345,33 @@ class InputModel {
|
||||
isPhysicalMouse.value = true;
|
||||
}
|
||||
if (isPhysicalMouse.value) {
|
||||
handleMouse(getEvent(e, _kMouseEventMove));
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position);
|
||||
}
|
||||
}
|
||||
|
||||
void onPointerPanZoomStart(PointerPanZoomStartEvent e) {
|
||||
_lastScale = 1.0;
|
||||
_stopFling = true;
|
||||
|
||||
if (peerPlatform == kPeerPlatformAndroid) {
|
||||
handlePointerEvent('touch', 'pan_start', e.position);
|
||||
}
|
||||
}
|
||||
|
||||
// https://docs.flutter.dev/release/breaking-changes/trackpad-gestures
|
||||
void onPointerPanZoomUpdate(PointerPanZoomUpdateEvent e) {
|
||||
final scale = ((e.scale - _lastScale) * 1000).toInt();
|
||||
_lastScale = e.scale;
|
||||
if (peerPlatform != kPeerPlatformAndroid) {
|
||||
final scale = ((e.scale - _lastScale) * 1000).toInt();
|
||||
_lastScale = e.scale;
|
||||
|
||||
if (scale != 0) {
|
||||
bind.sessionSendPointer(
|
||||
sessionId: sessionId,
|
||||
msg: json.encode({
|
||||
'touch': {'scale': scale}
|
||||
}));
|
||||
return;
|
||||
if (scale != 0) {
|
||||
bind.sessionSendPointer(
|
||||
sessionId: sessionId,
|
||||
msg: json.encode(
|
||||
PointerEventToRust(kPointerEventKindTouch, 'scale', scale)
|
||||
.toJson()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final delta = e.panDelta;
|
||||
@ -362,7 +379,7 @@ class InputModel {
|
||||
|
||||
var x = delta.dx.toInt();
|
||||
var y = delta.dy.toInt();
|
||||
if (parent.target?.ffiModel.pi.platform == kPeerPlatformLinux) {
|
||||
if (peerPlatform == kPeerPlatformLinux) {
|
||||
_trackpadScrollUnsent += (delta * _trackpadSpeed);
|
||||
x = _trackpadScrollUnsent.dx.truncate();
|
||||
y = _trackpadScrollUnsent.dy.truncate();
|
||||
@ -378,9 +395,13 @@ class InputModel {
|
||||
}
|
||||
}
|
||||
if (x != 0 || y != 0) {
|
||||
bind.sessionSendMouse(
|
||||
sessionId: sessionId,
|
||||
msg: '{"type": "trackpad", "x": "$x", "y": "$y"}');
|
||||
if (peerPlatform == kPeerPlatformAndroid) {
|
||||
handlePointerEvent('touch', 'pan_update', Offset(x.toDouble(), y.toDouble()));
|
||||
} else {
|
||||
bind.sessionSendMouse(
|
||||
sessionId: sessionId,
|
||||
msg: '{"type": "trackpad", "x": "$x", "y": "$y"}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,11 +457,15 @@ class InputModel {
|
||||
}
|
||||
|
||||
void onPointerPanZoomEnd(PointerPanZoomEndEvent e) {
|
||||
if (peerPlatform == kPeerPlatformAndroid) {
|
||||
handlePointerEvent('touch', 'pan_end', e.position);
|
||||
return;
|
||||
}
|
||||
|
||||
bind.sessionSendPointer(
|
||||
sessionId: sessionId,
|
||||
msg: json.encode({
|
||||
'touch': {'scale': 0}
|
||||
}));
|
||||
msg: json.encode(
|
||||
PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson()));
|
||||
|
||||
waitLastFlingDone();
|
||||
_stopFling = false;
|
||||
@ -465,21 +490,21 @@ class InputModel {
|
||||
}
|
||||
}
|
||||
if (isPhysicalMouse.value) {
|
||||
handleMouse(getEvent(e, _kMouseEventDown));
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventDown), e.position);
|
||||
}
|
||||
}
|
||||
|
||||
void onPointUpImage(PointerUpEvent e) {
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (isPhysicalMouse.value) {
|
||||
handleMouse(getEvent(e, _kMouseEventUp));
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventUp), e.position);
|
||||
}
|
||||
}
|
||||
|
||||
void onPointMoveImage(PointerMoveEvent e) {
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (isPhysicalMouse.value) {
|
||||
handleMouse(getEvent(e, _kMouseEventMove));
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position);
|
||||
}
|
||||
}
|
||||
|
||||
@ -504,19 +529,16 @@ class InputModel {
|
||||
}
|
||||
|
||||
void refreshMousePos() => handleMouse({
|
||||
'x': lastMousePos.dx,
|
||||
'y': lastMousePos.dy,
|
||||
'buttons': 0,
|
||||
'type': _kMouseEventMove,
|
||||
});
|
||||
}, lastMousePos);
|
||||
|
||||
void tryMoveEdgeOnExit(Offset pos) => handleMouse(
|
||||
{
|
||||
'x': pos.dx,
|
||||
'y': pos.dy,
|
||||
'buttons': 0,
|
||||
'type': _kMouseEventMove,
|
||||
},
|
||||
pos,
|
||||
onExit: true,
|
||||
);
|
||||
|
||||
@ -550,17 +572,49 @@ class InputModel {
|
||||
return Offset(x, y);
|
||||
}
|
||||
|
||||
void handleMouse(
|
||||
Map<String, dynamic> evt, {
|
||||
bool onExit = false,
|
||||
}) {
|
||||
double x = evt['x'];
|
||||
double y = max(0.0, evt['y']);
|
||||
final cursorModel = parent.target!.cursorModel;
|
||||
void handlePointerEvent(String kind, String type, Offset offset) {
|
||||
double x = offset.dx;
|
||||
double y = offset.dy;
|
||||
if (_checkPeerControlProtected(x, y)) {
|
||||
return;
|
||||
}
|
||||
// Only touch events are handled for now. So we can just ignore buttons.
|
||||
// to-do: handle mouse events
|
||||
|
||||
late final dynamic evtValue;
|
||||
if (type == 'pan_update') {
|
||||
evtValue = {
|
||||
'x': x.toInt(),
|
||||
'y': y.toInt(),
|
||||
};
|
||||
} else {
|
||||
final isMoveTypes = ['pan_start', 'pan_end'];
|
||||
final pos = handlePointerDevicePos(
|
||||
kPointerEventKindTouch,
|
||||
x,
|
||||
y,
|
||||
isMoveTypes.contains(type),
|
||||
type,
|
||||
);
|
||||
if (pos == null) {
|
||||
return;
|
||||
}
|
||||
evtValue = {
|
||||
'x': pos.x,
|
||||
'y': pos.y,
|
||||
};
|
||||
}
|
||||
|
||||
final evt = PointerEventToRust(kind, type, evtValue).toJson();
|
||||
bind.sessionSendPointer(
|
||||
sessionId: sessionId, msg: json.encode(modify(evt)));
|
||||
}
|
||||
|
||||
bool _checkPeerControlProtected(double x, double y) {
|
||||
final cursorModel = parent.target!.cursorModel;
|
||||
if (cursorModel.isPeerControlProtected) {
|
||||
lastMousePos = ui.Offset(x, y);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!cursorModel.gotMouseControl) {
|
||||
@ -571,10 +625,23 @@ class InputModel {
|
||||
cursorModel.gotMouseControl = true;
|
||||
} else {
|
||||
lastMousePos = ui.Offset(x, y);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
lastMousePos = ui.Offset(x, y);
|
||||
return false;
|
||||
}
|
||||
|
||||
void handleMouse(
|
||||
Map<String, dynamic> evt,
|
||||
Offset offset, {
|
||||
bool onExit = false,
|
||||
}) {
|
||||
double x = offset.dx;
|
||||
double y = max(0.0, offset.dy);
|
||||
if (_checkPeerControlProtected(x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var type = '';
|
||||
var isMove = false;
|
||||
@ -592,17 +659,58 @@ class InputModel {
|
||||
return;
|
||||
}
|
||||
evt['type'] = type;
|
||||
|
||||
final pos = handlePointerDevicePos(
|
||||
kPointerEventKindMouse,
|
||||
x,
|
||||
y,
|
||||
isMove,
|
||||
type,
|
||||
onExit: onExit,
|
||||
buttons: evt['buttons'],
|
||||
);
|
||||
if (pos == null) {
|
||||
return;
|
||||
}
|
||||
if (type != '') {
|
||||
evt['x'] = '0';
|
||||
evt['y'] = '0';
|
||||
} else {
|
||||
evt['x'] = '${pos.x}';
|
||||
evt['y'] = '${pos.y}';
|
||||
}
|
||||
|
||||
Map<int, String> mapButtons = {
|
||||
kPrimaryMouseButton: 'left',
|
||||
kSecondaryMouseButton: 'right',
|
||||
kMiddleMouseButton: 'wheel',
|
||||
kBackMouseButton: 'back',
|
||||
kForwardMouseButton: 'forward'
|
||||
};
|
||||
evt['buttons'] = mapButtons[evt['buttons']] ?? '';
|
||||
bind.sessionSendMouse(sessionId: sessionId, msg: json.encode(modify(evt)));
|
||||
}
|
||||
|
||||
Point? handlePointerDevicePos(
|
||||
String kind,
|
||||
double x,
|
||||
double y,
|
||||
bool isMove,
|
||||
String evtType, {
|
||||
bool onExit = false,
|
||||
int buttons = kPrimaryMouseButton,
|
||||
}) {
|
||||
y -= CanvasModel.topToEdge;
|
||||
x -= CanvasModel.leftToEdge;
|
||||
final canvasModel = parent.target!.canvasModel;
|
||||
final nearThr = 3;
|
||||
var nearRight = (canvasModel.size.width - x) < nearThr;
|
||||
var nearBottom = (canvasModel.size.height - y) < nearThr;
|
||||
|
||||
final ffiModel = parent.target!.ffiModel;
|
||||
if (isMove) {
|
||||
canvasModel.moveDesktopMouse(x, y);
|
||||
}
|
||||
|
||||
final nearThr = 3;
|
||||
var nearRight = (canvasModel.size.width - x) < nearThr;
|
||||
var nearBottom = (canvasModel.size.height - y) < nearThr;
|
||||
final d = ffiModel.display;
|
||||
final imageWidth = d.width * canvasModel.scale;
|
||||
final imageHeight = d.height * canvasModel.scale;
|
||||
@ -650,7 +758,7 @@ class InputModel {
|
||||
} catch (e) {
|
||||
debugPrintStack(
|
||||
label: 'canvasModel.scale value ${canvasModel.scale}, $e');
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
int minX = d.x.toInt();
|
||||
@ -659,40 +767,16 @@ class InputModel {
|
||||
int maxY = (d.y + d.height).toInt() - 1;
|
||||
evtX = trySetNearestRange(evtX, minX, maxX, 5);
|
||||
evtY = trySetNearestRange(evtY, minY, maxY, 5);
|
||||
if (evtX < minX || evtY < minY || evtX > maxX || evtY > maxY) {
|
||||
// If left mouse up, no early return.
|
||||
if (evt['buttons'] != kPrimaryMouseButton || type != 'up') {
|
||||
return;
|
||||
if (kind == kPointerEventKindMouse) {
|
||||
if (evtX < minX || evtY < minY || evtX > maxX || evtY > maxY) {
|
||||
// If left mouse up, no early return.
|
||||
if (!(buttons == kPrimaryMouseButton && evtType == 'up')) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type != '') {
|
||||
evtX = 0;
|
||||
evtY = 0;
|
||||
}
|
||||
|
||||
evt['x'] = '$evtX';
|
||||
evt['y'] = '$evtY';
|
||||
var buttons = '';
|
||||
switch (evt['buttons']) {
|
||||
case kPrimaryMouseButton:
|
||||
buttons = 'left';
|
||||
break;
|
||||
case kSecondaryMouseButton:
|
||||
buttons = 'right';
|
||||
break;
|
||||
case kMiddleMouseButton:
|
||||
buttons = 'wheel';
|
||||
break;
|
||||
case kBackMouseButton:
|
||||
buttons = 'back';
|
||||
break;
|
||||
case kForwardMouseButton:
|
||||
buttons = 'forward';
|
||||
break;
|
||||
}
|
||||
evt['buttons'] = buttons;
|
||||
bind.sessionSendMouse(sessionId: sessionId, msg: json.encode(evt));
|
||||
return Point(evtX, evtY);
|
||||
}
|
||||
|
||||
/// Web only
|
||||
|
@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
@ -41,7 +42,50 @@ final _waitForImageDialogShow = <UuidValue, bool>{};
|
||||
final _waitForFirstImage = <UuidValue, bool>{};
|
||||
final _constSessionId = Uuid().v4obj();
|
||||
|
||||
class CachedPeerData {
|
||||
Map<String, dynamic> updatePrivacyMode = {};
|
||||
Map<String, dynamic> peerInfo = {};
|
||||
List<Map<String, dynamic>> cursorDataList = [];
|
||||
Map<String, dynamic> lastCursorId = {};
|
||||
bool secure = false;
|
||||
bool direct = false;
|
||||
|
||||
CachedPeerData();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode({
|
||||
'updatePrivacyMode': updatePrivacyMode,
|
||||
'peerInfo': peerInfo,
|
||||
'cursorDataList': cursorDataList,
|
||||
'lastCursorId': lastCursorId,
|
||||
'secure': secure,
|
||||
'direct': direct,
|
||||
});
|
||||
}
|
||||
|
||||
static CachedPeerData? fromString(String s) {
|
||||
try {
|
||||
final map = jsonDecode(s);
|
||||
final data = CachedPeerData();
|
||||
data.updatePrivacyMode = map['updatePrivacyMode'];
|
||||
data.peerInfo = map['peerInfo'];
|
||||
for (final cursorData in map['cursorDataList']) {
|
||||
data.cursorDataList.add(cursorData);
|
||||
}
|
||||
data.lastCursorId = map['lastCursorId'];
|
||||
data.secure = map['secure'];
|
||||
data.direct = map['direct'];
|
||||
return data;
|
||||
} catch (e) {
|
||||
debugPrint('Failed to parse CachedPeerData: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FfiModel with ChangeNotifier {
|
||||
CachedPeerData cachedPeerData = CachedPeerData();
|
||||
PeerInfo _pi = PeerInfo();
|
||||
Display _display = Display();
|
||||
|
||||
@ -117,6 +161,8 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
setConnectionType(String peerId, bool secure, bool direct) {
|
||||
cachedPeerData.secure = secure;
|
||||
cachedPeerData.direct = direct;
|
||||
_secure = secure;
|
||||
_direct = direct;
|
||||
try {
|
||||
@ -143,6 +189,22 @@ class FfiModel with ChangeNotifier {
|
||||
_permissions.clear();
|
||||
}
|
||||
|
||||
handleCachedPeerData(CachedPeerData data, String peerId) async {
|
||||
handleMsgBox({
|
||||
'type': 'success',
|
||||
'title': 'Successful',
|
||||
'text': 'Connected, waiting for image...',
|
||||
'link': '',
|
||||
}, sessionId, peerId);
|
||||
updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId);
|
||||
setConnectionType(peerId, data.secure, data.direct);
|
||||
handlePeerInfo(data.peerInfo, peerId);
|
||||
for (var element in data.cursorDataList) {
|
||||
handleCursorData(element);
|
||||
}
|
||||
handleCursorId(data.lastCursorId);
|
||||
}
|
||||
|
||||
// todo: why called by two position
|
||||
StreamEventHandler startEventListener(SessionID sessionId, String peerId) {
|
||||
return (evt) async {
|
||||
@ -159,9 +221,9 @@ class FfiModel with ChangeNotifier {
|
||||
} else if (name == 'switch_display') {
|
||||
handleSwitchDisplay(evt, sessionId, peerId);
|
||||
} else if (name == 'cursor_data') {
|
||||
await parent.target?.cursorModel.updateCursorData(evt);
|
||||
await handleCursorData(evt);
|
||||
} else if (name == 'cursor_id') {
|
||||
await parent.target?.cursorModel.updateCursorId(evt);
|
||||
await handleCursorId(evt);
|
||||
} else if (name == 'cursor_position') {
|
||||
await parent.target?.cursorModel.updateCursorPosition(evt, peerId);
|
||||
} else if (name == 'clipboard') {
|
||||
@ -464,6 +526,8 @@ class FfiModel with ChangeNotifier {
|
||||
|
||||
/// Handle the peer info event based on [evt].
|
||||
handlePeerInfo(Map<String, dynamic> evt, String peerId) async {
|
||||
cachedPeerData.peerInfo = evt;
|
||||
|
||||
// recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
|
||||
bind.mainLoadRecentPeers();
|
||||
|
||||
@ -579,9 +643,20 @@ class FfiModel with ChangeNotifier {
|
||||
return d;
|
||||
}
|
||||
|
||||
handleCursorId(Map<String, dynamic> evt) async {
|
||||
cachedPeerData.lastCursorId = evt;
|
||||
await parent.target?.cursorModel.updateCursorId(evt);
|
||||
}
|
||||
|
||||
handleCursorData(Map<String, dynamic> evt) async {
|
||||
cachedPeerData.cursorDataList.add(evt);
|
||||
await parent.target?.cursorModel.updateCursorData(evt);
|
||||
}
|
||||
|
||||
/// Handle the peer info synchronization event based on [evt].
|
||||
handleSyncPeerInfo(Map<String, dynamic> evt, SessionID sessionId) async {
|
||||
if (evt['displays'] != null) {
|
||||
cachedPeerData.peerInfo['displays'] = evt['displays'];
|
||||
List<dynamic> displays = json.decode(evt['displays']);
|
||||
List<Display> newDisplays = [];
|
||||
for (int i = 0; i < displays.length; ++i) {
|
||||
@ -1596,7 +1671,6 @@ class FFI {
|
||||
/// dialogManager use late to ensure init after main page binding [globalKey]
|
||||
late final dialogManager = OverlayDialogManager();
|
||||
|
||||
late final bool isSessionAdded;
|
||||
late final SessionID sessionId;
|
||||
late final ImageModel imageModel; // session
|
||||
late final FfiModel ffiModel; // session
|
||||
@ -1615,7 +1689,6 @@ class FFI {
|
||||
late final ElevationModel elevationModel; // session
|
||||
|
||||
FFI(SessionID? sId) {
|
||||
isSessionAdded = sId != null;
|
||||
sessionId = sId ?? (isDesktop ? Uuid().v4obj() : _constSessionId);
|
||||
imageModel = ImageModel(WeakReference(this));
|
||||
ffiModel = FfiModel(WeakReference(this));
|
||||
@ -1641,7 +1714,8 @@ class FFI {
|
||||
bool isRdp = false,
|
||||
String? switchUuid,
|
||||
String? password,
|
||||
bool? forceRelay}) {
|
||||
bool? forceRelay,
|
||||
int? tabWindowId}) {
|
||||
closed = false;
|
||||
auditNote = '';
|
||||
assert(!(isFileTransfer && isPortForward), 'more than one connect type');
|
||||
@ -1656,7 +1730,9 @@ class FFI {
|
||||
imageModel.id = id;
|
||||
cursorModel.id = id;
|
||||
}
|
||||
if (!isSessionAdded) {
|
||||
// If tabWindowId != null, this session is a "tab -> window" one.
|
||||
// Else this session is a new one.
|
||||
if (tabWindowId == null) {
|
||||
// ignore: unused_local_variable
|
||||
final addRes = bind.sessionAddSync(
|
||||
sessionId: sessionId,
|
||||
@ -1677,8 +1753,25 @@ class FFI {
|
||||
// Preserved for the rgba data.
|
||||
stream.listen((message) {
|
||||
if (closed) return;
|
||||
if (isSessionAdded && !isToNewWindowNotified.value) {
|
||||
bind.sessionReadyToNewWindow(sessionId: sessionId);
|
||||
if (tabWindowId != null && !isToNewWindowNotified.value) {
|
||||
// Session is read to be moved to a new window.
|
||||
// Get the cached data and handle the cached data.
|
||||
Future.delayed(Duration.zero, () async {
|
||||
final cachedData = await DesktopMultiWindow.invokeMethod(
|
||||
tabWindowId, kWindowEventGetCachedSessionData, id);
|
||||
if (cachedData == null) {
|
||||
// unreachable
|
||||
debugPrint('Unreachable, the cached data is empty.');
|
||||
return;
|
||||
}
|
||||
final data = CachedPeerData.fromString(cachedData);
|
||||
if (data == null) {
|
||||
debugPrint('Unreachable, the cached data cannot be decoded.');
|
||||
return;
|
||||
}
|
||||
ffiModel.handleCachedPeerData(data, id);
|
||||
await bind.sessionRefresh(sessionId: sessionId);
|
||||
});
|
||||
isToNewWindowNotified.value = true;
|
||||
}
|
||||
() async {
|
||||
|
@ -28,6 +28,13 @@ extension Index on int {
|
||||
}
|
||||
}
|
||||
|
||||
class MultiWindowCallResult {
|
||||
int windowId;
|
||||
dynamic result;
|
||||
|
||||
MultiWindowCallResult(this.windowId, this.result);
|
||||
}
|
||||
|
||||
/// Window Manager
|
||||
/// mainly use it in `Main Window`
|
||||
/// use it in sub window is not recommended
|
||||
@ -47,6 +54,7 @@ class RustDeskMultiWindowManager {
|
||||
var params = {
|
||||
'type': WindowType.RemoteDesktop.index,
|
||||
'id': peerId,
|
||||
'tab_window_id': windowId,
|
||||
'session_id': sessionId,
|
||||
};
|
||||
await _newSession(
|
||||
@ -57,17 +65,15 @@ class RustDeskMultiWindowManager {
|
||||
_remoteDesktopWindows,
|
||||
jsonEncode(params),
|
||||
);
|
||||
await DesktopMultiWindow.invokeMethod(
|
||||
windowId, kWindowEventCloseForSeparateWindow, peerId);
|
||||
}
|
||||
|
||||
newSessionWindow(
|
||||
Future<int> newSessionWindow(
|
||||
WindowType type, String remoteId, String msg, List<int> windows) async {
|
||||
final windowController = await DesktopMultiWindow.createWindow(msg);
|
||||
final windowId = windowController.windowId;
|
||||
windowController
|
||||
..setFrame(const Offset(0, 0) &
|
||||
Size(1280 + windowController.windowId * 20,
|
||||
720 + windowController.windowId * 20))
|
||||
..setFrame(
|
||||
const Offset(0, 0) & Size(1280 + windowId * 20, 720 + windowId * 20))
|
||||
..center()
|
||||
..setTitle(getWindowNameWithId(
|
||||
remoteId,
|
||||
@ -76,11 +82,12 @@ class RustDeskMultiWindowManager {
|
||||
if (Platform.isMacOS) {
|
||||
Future.microtask(() => windowController.show());
|
||||
}
|
||||
registerActiveWindow(windowController.windowId);
|
||||
windows.add(windowController.windowId);
|
||||
registerActiveWindow(windowId);
|
||||
windows.add(windowId);
|
||||
return windowId;
|
||||
}
|
||||
|
||||
_newSession(
|
||||
Future<MultiWindowCallResult> _newSession(
|
||||
bool openInTabs,
|
||||
WindowType type,
|
||||
String methodName,
|
||||
@ -90,9 +97,10 @@ class RustDeskMultiWindowManager {
|
||||
) async {
|
||||
if (openInTabs) {
|
||||
if (windows.isEmpty) {
|
||||
await newSessionWindow(type, remoteId, msg, windows);
|
||||
final windowId = await newSessionWindow(type, remoteId, msg, windows);
|
||||
return MultiWindowCallResult(windowId, null);
|
||||
} else {
|
||||
call(type, methodName, msg);
|
||||
return call(type, methodName, msg);
|
||||
}
|
||||
} else {
|
||||
if (_inactiveWindows.isNotEmpty) {
|
||||
@ -103,15 +111,16 @@ class RustDeskMultiWindowManager {
|
||||
await DesktopMultiWindow.invokeMethod(windowId, methodName, msg);
|
||||
WindowController.fromWindowId(windowId).show();
|
||||
registerActiveWindow(windowId);
|
||||
return;
|
||||
return MultiWindowCallResult(windowId, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
await newSessionWindow(type, remoteId, msg, windows);
|
||||
final windowId = await newSessionWindow(type, remoteId, msg, windows);
|
||||
return MultiWindowCallResult(windowId, null);
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> newSession(
|
||||
Future<MultiWindowCallResult> newSession(
|
||||
WindowType type,
|
||||
String methodName,
|
||||
String remoteId,
|
||||
@ -143,15 +152,15 @@ class RustDeskMultiWindowManager {
|
||||
for (final windowId in windows) {
|
||||
if (await DesktopMultiWindow.invokeMethod(
|
||||
windowId, kWindowEventActiveSession, remoteId)) {
|
||||
return;
|
||||
return MultiWindowCallResult(windowId, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _newSession(openInTabs, type, methodName, remoteId, windows, msg);
|
||||
return _newSession(openInTabs, type, methodName, remoteId, windows, msg);
|
||||
}
|
||||
|
||||
Future<dynamic> newRemoteDesktop(
|
||||
Future<MultiWindowCallResult> newRemoteDesktop(
|
||||
String remoteId, {
|
||||
String? password,
|
||||
String? switchUuid,
|
||||
@ -168,7 +177,7 @@ class RustDeskMultiWindowManager {
|
||||
);
|
||||
}
|
||||
|
||||
Future<dynamic> newFileTransfer(String remoteId,
|
||||
Future<MultiWindowCallResult> newFileTransfer(String remoteId,
|
||||
{String? password, bool? forceRelay}) async {
|
||||
return await newSession(
|
||||
WindowType.FileTransfer,
|
||||
@ -180,7 +189,7 @@ class RustDeskMultiWindowManager {
|
||||
);
|
||||
}
|
||||
|
||||
Future<dynamic> newPortForward(String remoteId, bool isRDP,
|
||||
Future<MultiWindowCallResult> newPortForward(String remoteId, bool isRDP,
|
||||
{String? password, bool? forceRelay}) async {
|
||||
return await newSession(
|
||||
WindowType.PortForward,
|
||||
@ -193,18 +202,22 @@ class RustDeskMultiWindowManager {
|
||||
);
|
||||
}
|
||||
|
||||
Future<dynamic> call(WindowType type, String methodName, dynamic args) async {
|
||||
Future<MultiWindowCallResult> call(
|
||||
WindowType type, String methodName, dynamic args) async {
|
||||
final wnds = _findWindowsByType(type);
|
||||
if (wnds.isEmpty) {
|
||||
return;
|
||||
return MultiWindowCallResult(kInvalidWindowId, null);
|
||||
}
|
||||
for (final windowId in wnds) {
|
||||
if (_activeWindows.contains(windowId)) {
|
||||
return await DesktopMultiWindow.invokeMethod(
|
||||
windowId, methodName, args);
|
||||
final res =
|
||||
await DesktopMultiWindow.invokeMethod(windowId, methodName, args);
|
||||
return MultiWindowCallResult(windowId, res);
|
||||
}
|
||||
}
|
||||
return await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args);
|
||||
final res =
|
||||
await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args);
|
||||
return MultiWindowCallResult(wnds[0], res);
|
||||
}
|
||||
|
||||
List<int> _findWindowsByType(WindowType type) {
|
||||
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
|
||||
version: 1.2.2
|
||||
version: 1.2.3
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0"
|
||||
@ -97,6 +97,7 @@ dependencies:
|
||||
dropdown_button2: ^2.0.0
|
||||
uuid: ^3.0.7
|
||||
auto_size_text_field: ^2.2.1
|
||||
flex_color_picker: ^3.3.0
|
||||
|
||||
dev_dependencies:
|
||||
icons_launcher: ^2.0.4
|
||||
|
@ -118,9 +118,29 @@ message TouchScaleUpdate {
|
||||
int32 scale = 1;
|
||||
}
|
||||
|
||||
message TouchPanStart {
|
||||
int32 x = 1;
|
||||
int32 y = 2;
|
||||
}
|
||||
|
||||
message TouchPanUpdate {
|
||||
// The delta x position relative to the previous position.
|
||||
int32 x = 1;
|
||||
// The delta y position relative to the previous position.
|
||||
int32 y = 2;
|
||||
}
|
||||
|
||||
message TouchPanEnd {
|
||||
int32 x = 1;
|
||||
int32 y = 2;
|
||||
}
|
||||
|
||||
message TouchEvent {
|
||||
oneof union {
|
||||
TouchScaleUpdate scale_update = 1;
|
||||
TouchPanStart pan_start = 2;
|
||||
TouchPanUpdate pan_update = 3;
|
||||
TouchPanEnd pan_end = 4;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1525,6 +1525,12 @@ pub struct Ab {
|
||||
pub peers: Vec<AbPeer>,
|
||||
#[serde(default, deserialize_with = "deserialize_vec_string")]
|
||||
pub tags: Vec<String>,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
skip_serializing_if = "String::is_empty"
|
||||
)]
|
||||
pub tag_colors: String,
|
||||
}
|
||||
|
||||
impl Ab {
|
||||
|
@ -154,17 +154,18 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_init(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_main_service_mouse_input(mask: i32, x: i32, y: i32) -> JniResult<()> {
|
||||
pub fn call_main_service_pointer_input(kind: &str, mask: i32, x: i32, y: i32) -> JniResult<()> {
|
||||
if let (Some(jvm), Some(ctx)) = (
|
||||
JVM.read().unwrap().as_ref(),
|
||||
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
||||
) {
|
||||
let mut env = jvm.attach_current_thread_as_daemon()?;
|
||||
let kind = env.new_string(kind)?;
|
||||
env.call_method(
|
||||
ctx,
|
||||
"rustMouseInput",
|
||||
"(III)V",
|
||||
&[JValue::Int(mask), JValue::Int(x), JValue::Int(y)],
|
||||
"rustPointerInput",
|
||||
"(Ljava/lang/String;III)V",
|
||||
&[JValue::Object(&JObject::from(kind)), JValue::Int(mask), JValue::Int(x), JValue::Int(y)],
|
||||
)?;
|
||||
return Ok(());
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
pkgname=rustdesk
|
||||
pkgver=1.2.2
|
||||
pkgver=1.2.3
|
||||
pkgrel=0
|
||||
epoch=
|
||||
pkgdesc=""
|
||||
|
@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.2
|
||||
Version: 1.2.3
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.2
|
||||
Version: 1.2.3
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.2
|
||||
Version: 1.2.3
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
@ -2387,7 +2387,7 @@ pub trait Interface: Send + Clone + 'static + Sized {
|
||||
fn send(&self, data: Data);
|
||||
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str);
|
||||
fn handle_login_error(&mut self, err: &str) -> bool;
|
||||
fn handle_peer_info(&mut self, pi: PeerInfo, is_cached_pi: bool);
|
||||
fn handle_peer_info(&mut self, pi: PeerInfo);
|
||||
fn on_error(&self, err: &str) {
|
||||
self.msgbox("error", "Error", err, "");
|
||||
}
|
||||
|
@ -125,18 +125,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
.await
|
||||
{
|
||||
Ok((mut peer, direct, pk)) => {
|
||||
let is_secured = peer.is_secured();
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
self.handler
|
||||
.cache_flutter
|
||||
.write()
|
||||
.unwrap()
|
||||
.is_secured_direct
|
||||
.replace((is_secured, direct));
|
||||
}
|
||||
self.handler.set_connection_type(is_secured, direct); // flutter -> connection_ready
|
||||
self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready
|
||||
self.handler.update_direct(Some(direct));
|
||||
if conn_type == ConnType::DEFAULT_CONN {
|
||||
self.handler
|
||||
@ -1021,12 +1010,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
Some(login_response::Union::PeerInfo(pi)) => {
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
self.handler.cache_flutter.write().unwrap().pi = pi.clone();
|
||||
}
|
||||
self.handler.handle_peer_info(pi, false);
|
||||
self.handler.handle_peer_info(pi);
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
self.check_clipboard_file_context();
|
||||
if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) {
|
||||
@ -1073,22 +1057,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::CursorData(cd)) => {
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
let mut lock = self.handler.cache_flutter.write().unwrap();
|
||||
if !lock.cursor_data.contains_key(&cd.id) {
|
||||
lock.cursor_data.insert(cd.id, cd.clone());
|
||||
}
|
||||
}
|
||||
self.handler.set_cursor_data(cd);
|
||||
}
|
||||
Some(message::Union::CursorId(id)) => {
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
self.handler.cache_flutter.write().unwrap().cursor_id = id;
|
||||
}
|
||||
self.handler.set_cursor_id(id.to_string());
|
||||
}
|
||||
Some(message::Union::CursorPosition(cp)) => {
|
||||
@ -1305,16 +1276,6 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
Some(misc::Union::SwitchDisplay(s)) => {
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
self.handler
|
||||
.cache_flutter
|
||||
.write()
|
||||
.unwrap()
|
||||
.sp
|
||||
.replace(s.clone());
|
||||
}
|
||||
self.handler.handle_peer_switch_display(&s);
|
||||
self.video_sender.send(MediaData::Reset).ok();
|
||||
if s.width > 0 && s.height > 0 {
|
||||
@ -1506,12 +1467,6 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
Some(message::Union::PeerInfo(pi)) => {
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
self.handler.cache_flutter.write().unwrap().pi.displays =
|
||||
pi.displays.clone();
|
||||
}
|
||||
self.handler.set_displays(&pi.displays);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -831,30 +831,19 @@ pub fn check_software_update() {
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn check_software_update_() -> hbb_common::ResultType<()> {
|
||||
sleep(3.).await;
|
||||
let url = "https://github.com/rustdesk/rustdesk/releases/latest";
|
||||
let latest_release_response = reqwest::get(url).await?;
|
||||
let latest_release_version = latest_release_response
|
||||
.url()
|
||||
.path()
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
let rendezvous_server = format!("rs-sg.rustdesk.com:{}", config::RENDEZVOUS_PORT);
|
||||
let (mut socket, rendezvous_server) =
|
||||
socket_client::new_udp_for(&rendezvous_server, CONNECT_TIMEOUT).await?;
|
||||
let response_url = latest_release_response.url().to_string();
|
||||
|
||||
let mut msg_out = RendezvousMessage::new();
|
||||
msg_out.set_software_update(SoftwareUpdate {
|
||||
url: crate::VERSION.to_owned(),
|
||||
..Default::default()
|
||||
});
|
||||
socket.send(&msg_out, rendezvous_server).await?;
|
||||
use hbb_common::protobuf::Message;
|
||||
for _ in 0..2 {
|
||||
if let Some(Ok((bytes, _))) = socket.next_timeout(READ_TIMEOUT).await {
|
||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
||||
if let Some(rendezvous_message::Union::SoftwareUpdate(su)) = msg_in.union {
|
||||
let version = hbb_common::get_version_from_url(&su.url);
|
||||
if get_version_number(&version) > get_version_number(crate::VERSION) {
|
||||
*SOFTWARE_UPDATE_URL.lock().unwrap() = su.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if get_version_number(&latest_release_version) > get_version_number(crate::VERSION) {
|
||||
*SOFTWARE_UPDATE_URL.lock().unwrap() = response_url;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -36,9 +36,11 @@ pub(crate) const APP_TYPE_CM: &str = "cm";
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub(crate) const APP_TYPE_CM: &str = "main";
|
||||
|
||||
pub(crate) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
|
||||
pub(crate) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
||||
pub(crate) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward";
|
||||
// Do not remove the following constants.
|
||||
// Uncomment them when they are used.
|
||||
// pub(crate) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
|
||||
// pub(crate) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
||||
// pub(crate) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub(crate) static ref CUR_SESSION_ID: RwLock<SessionID> = Default::default();
|
||||
@ -1130,6 +1132,85 @@ pub fn stop_global_event_stream(app_type: String) {
|
||||
let _ = GLOBAL_EVENT_STREAM.write().unwrap().remove(&app_type);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn session_send_touch_scale(
|
||||
session_id: SessionID,
|
||||
v: &serde_json::Value,
|
||||
alt: bool,
|
||||
ctrl: bool,
|
||||
shift: bool,
|
||||
command: bool,
|
||||
) {
|
||||
match v.get("v").and_then(|s| s.as_i64()) {
|
||||
Some(scale) => {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn session_send_touch_pan(
|
||||
session_id: SessionID,
|
||||
v: &serde_json::Value,
|
||||
pan_event: &str,
|
||||
alt: bool,
|
||||
ctrl: bool,
|
||||
shift: bool,
|
||||
command: bool,
|
||||
) {
|
||||
match v.get("v") {
|
||||
Some(v) => match (
|
||||
v.get("x").and_then(|x| x.as_i64()),
|
||||
v.get("y").and_then(|y| y.as_i64()),
|
||||
) {
|
||||
(Some(x), Some(y)) => {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
session
|
||||
.send_touch_pan_event(pan_event, x as _, y as _, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn session_send_touch_event(
|
||||
session_id: SessionID,
|
||||
v: &serde_json::Value,
|
||||
alt: bool,
|
||||
ctrl: bool,
|
||||
shift: bool,
|
||||
command: bool,
|
||||
) {
|
||||
match v.get("t").and_then(|t| t.as_str()) {
|
||||
Some("scale") => session_send_touch_scale(session_id, v, alt, ctrl, shift, command),
|
||||
Some(pan_event) => {
|
||||
session_send_touch_pan(session_id, v, pan_event, alt, ctrl, shift, command)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&msg) {
|
||||
let alt = m.get("alt").is_some();
|
||||
let ctrl = m.get("ctrl").is_some();
|
||||
let shift = m.get("shift").is_some();
|
||||
let command = m.get("command").is_some();
|
||||
match (m.get("k"), m.get("v")) {
|
||||
(Some(k), Some(v)) => match k.as_str() {
|
||||
Some("touch") => session_send_touch_event(session_id, v, alt, ctrl, shift, command),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn get_rgba() {}
|
||||
|
||||
|
@ -597,14 +597,6 @@ pub fn session_change_resolution(session_id: SessionID, display: i32, width: i32
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_ready_to_new_window(session_id: SessionID) {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
||||
session.restore_flutter_cache();
|
||||
session.refresh_video();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_session_id) {
|
||||
@ -1179,21 +1171,7 @@ pub fn main_load_ab() -> String {
|
||||
}
|
||||
|
||||
pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&msg) {
|
||||
let alt = m.get("alt").is_some();
|
||||
let ctrl = m.get("ctrl").is_some();
|
||||
let shift = m.get("shift").is_some();
|
||||
let command = m.get("command").is_some();
|
||||
if let Some(touch_event) = m.get("touch") {
|
||||
if let Some(scale) = touch_event.get("scale") {
|
||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||
if let Some(scale) = scale.as_i64() {
|
||||
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
super::flutter::session_send_pointer(session_id, msg);
|
||||
}
|
||||
|
||||
pub fn session_send_mouse(session_id: SessionID, msg: String) {
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", "未成功获取地址簿"),
|
||||
("push_ab_failed_tip", "未成功上传地址簿"),
|
||||
("synced_peer_readded_tip", "最近会话中存在的设备将会被重新同步到地址簿。"),
|
||||
("Change Color", "更改颜色"),
|
||||
("Primary Color", "基本色"),
|
||||
("HSV Color", "HSV 色"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", "Nepodařilo se obnovit adresář"),
|
||||
("push_ab_failed_tip", "Nepodařilo se synchronizovat adresář se serverem"),
|
||||
("synced_peer_readded_tip", "Zařízení, která byla přítomna v posledních relacích, budou synchronizována zpět do adresáře."),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", "Aktualisierung des Adressbuchs fehlgeschlagen"),
|
||||
("push_ab_failed_tip", "Synchronisierung des Adressbuchs mit dem Server fehlgeschlagen"),
|
||||
("synced_peer_readded_tip", "Die Geräte, die in den letzten Sitzungen vorhanden waren, werden erneut zum Adressbuch hinzugefügt."),
|
||||
("Change Color", "Farbe ändern"),
|
||||
("Primary Color", "Primärfarbe"),
|
||||
("HSV Color", "HSV-Farbe"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", "No se ha podido refrescar el directorio"),
|
||||
("push_ab_failed_tip", "No se ha podido sincronizar el directorio con el servidor"),
|
||||
("synced_peer_readded_tip", "Los dispositivos presentes en sesiones recientes se sincronizarán con el directorio."),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you want to enter again?", "Voulez-vous participer à nouveau ?"),
|
||||
("Connection Error", "Erreur de connexion"),
|
||||
("Error", "Erreur"),
|
||||
("Reset by the peer", "La connexion a été fermée par la machine distante"),
|
||||
("Reset by the peer", "La connexion a été fermée par l'appareil distant"),
|
||||
("Connecting...", "Connexion..."),
|
||||
("Connection in progress. Please wait.", "Connexion en cours. Veuillez patienter."),
|
||||
("Please try 1 minute later", "Réessayez dans une minute"),
|
||||
@ -92,8 +92,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Refresh File", "Rafraîchir le contenu"),
|
||||
("Local", "Local"),
|
||||
("Remote", "Distant"),
|
||||
("Remote Computer", "Ordinateur distant"),
|
||||
("Local Computer", "Ordinateur local"),
|
||||
("Remote Computer", "Appareil distant"),
|
||||
("Local Computer", "Appareil local"),
|
||||
("Confirm Delete", "Confirmer la suppression"),
|
||||
("Delete", "Supprimer"),
|
||||
("Properties", "Propriétés"),
|
||||
@ -129,9 +129,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show remote cursor", "Afficher le curseur distant"),
|
||||
("Show quality monitor", "Afficher le moniteur de qualité"),
|
||||
("Disable clipboard", "Désactiver le presse-papier"),
|
||||
("Lock after session end", "Verrouiller l'ordinateur distant après la déconnexion"),
|
||||
("Lock after session end", "Verrouiller l'appareil distant après la déconnexion"),
|
||||
("Insert", "Envoyer"),
|
||||
("Insert Lock", "Verrouiller l'ordinateur distant"),
|
||||
("Insert Lock", "Verrouiller l'appareil distant"),
|
||||
("Refresh", "Rafraîchir l'écran"),
|
||||
("ID does not exist", "L'ID n'existe pas"),
|
||||
("Failed to connect to rendezvous server", "Échec de la connexion au serveur rendezvous"),
|
||||
@ -188,7 +188,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Relayed and encrypted connection", "Connexion relais chiffrée"),
|
||||
("Direct and unencrypted connection", "Connexion directe non chiffrée"),
|
||||
("Relayed and unencrypted connection", "Connexion relais non chiffrée"),
|
||||
("Enter Remote ID", "Entrer l'ID de l'appareil à distance"),
|
||||
("Enter Remote ID", "Entrer l'ID de l'appareil distant"),
|
||||
("Enter your password", "Entrer votre mot de passe"),
|
||||
("Logging in...", "En cours de connexion ..."),
|
||||
("Enable RDP session sharing", "Activer le partage de session RDP"),
|
||||
@ -210,7 +210,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Settings", "Paramètres"),
|
||||
("Username", " Nom d'utilisateur"),
|
||||
("Invalid port", "Port invalide"),
|
||||
("Closed manually by the peer", "Fermé manuellement par la machine distante"),
|
||||
("Closed manually by the peer", "Fermé manuellement par l'appareil distant"),
|
||||
("Enable remote configuration modification", "Autoriser la modification de la configuration à distance"),
|
||||
("Run without install", "Exécuter sans installer"),
|
||||
("Connect via relay", "Connexion via relais"),
|
||||
@ -223,12 +223,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Verification code", "Code de vérification"),
|
||||
("verification_tip", "Un nouvel appareil a été détecté et un code de vérification a été envoyé à l'adresse e-mail enregistrée, entrez le code de vérification pour continuer la connexion."),
|
||||
("Logout", "Déconnexion"),
|
||||
("Tags", "Étiqueter"),
|
||||
("Tags", "Étiquettes"),
|
||||
("Search ID", "Rechercher un ID"),
|
||||
("whitelist_sep", "Vous pouvez utiliser une virgule, un point-virgule, un espace ou une nouvelle ligne comme séparateur"),
|
||||
("Add ID", "Ajouter un ID"),
|
||||
("Add Tag", "Ajouter une balise"),
|
||||
("Unselect all tags", "Désélectionner toutes les balises"),
|
||||
("Unselect all tags", "Désélectionner toutes les étiquettes"),
|
||||
("Network error", "Erreur réseau"),
|
||||
("Username missed", "Nom d'utilisateur manquant"),
|
||||
("Password missed", "Mot de passe manquant"),
|
||||
@ -274,7 +274,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Chat", "Discuter"),
|
||||
("Total", "Total"),
|
||||
("items", "éléments"),
|
||||
("Selected", "Sélectionné"),
|
||||
("Selected", "Sélectionné(s)"),
|
||||
("Screen Capture", "Capture d'écran"),
|
||||
("Input Control", "Contrôle de saisie"),
|
||||
("Audio Capture", "Capture audio"),
|
||||
@ -301,9 +301,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Succeeded", "Succès"),
|
||||
("Someone turns on privacy mode, exit", "Quelqu'un active le mode de confidentialité, quittez"),
|
||||
("Unsupported", "Non pris en charge"),
|
||||
("Peer denied", "Machine distante refusée"),
|
||||
("Peer denied", "Appareil distant refusé"),
|
||||
("Please install plugins", "Veuillez installer les plugins"),
|
||||
("Peer exit", "Machine distante déconnectée"),
|
||||
("Peer exit", "Appareil distant déconnecté"),
|
||||
("Failed to turn off", "Échec de la désactivation"),
|
||||
("Turned off", "Désactivé"),
|
||||
("In privacy mode", "en mode privé"),
|
||||
@ -382,7 +382,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Write a message", "Ecrire un message"),
|
||||
("Prompt", ""),
|
||||
("Please wait for confirmation of UAC...", "Veuillez attendre la confirmation de l'UAC..."),
|
||||
("elevated_foreground_window_tip", "La fenêtre actuelle que la machine distante nécessite des privilèges plus élevés pour fonctionner, elle ne peut donc pas être atteinte par la souris et le clavier. Vous pouvez demander à l'utilisateur distant de réduire la fenêtre actuelle ou de cliquer sur le bouton d'élévation dans la fenêtre de gestion des connexions. Pour éviter ce problème, il est recommandé d'installer le logiciel sur l'appareil distant."),
|
||||
("elevated_foreground_window_tip", "La fenêtre actuelle que l'appareil distant nécessite des privilèges plus élevés pour fonctionner, elle ne peut donc pas être atteinte par la souris et le clavier. Vous pouvez demander à l'utilisateur distant de réduire la fenêtre actuelle ou de cliquer sur le bouton d'élévation dans la fenêtre de gestion des connexions. Pour éviter ce problème, il est recommandé d'installer le logiciel sur l'appareil distant."),
|
||||
("Disconnected", "Déconnecté"),
|
||||
("Other", "Divers"),
|
||||
("Confirm before closing multiple tabs", "Confirmer avant de fermer plusieurs onglets"),
|
||||
@ -392,7 +392,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland nécessite Ubuntu 21.04 ou une version supérieure."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland nécessite une version supérieure de la distribution Linux. Veuillez essayer le bureau X11 ou changer votre système d'exploitation."),
|
||||
("JumpLink", "Afficher"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (côté machine distante)."),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (côté appareil distant)."),
|
||||
("Show RustDesk", "Afficher RustDesk"),
|
||||
("This PC", "Ce PC"),
|
||||
("or", "ou"),
|
||||
@ -454,7 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Voice call", "Appel voix"),
|
||||
("Text chat", "Conversation textuelfle"),
|
||||
("Stop voice call", "Stopper l'appel voix"),
|
||||
("relay_hint_tip", "Il se peut qu'il ne doit pas possible de se connecter directement, vous pouvez essayer de vous connecter via un relais. \nEn outre, si vous souhaitez utiliser directement le relais, vous pouvez ajouter le suffixe \"/r\" à l'ID ou sélectionner l'option \"Toujours se connecter via le relais\" dans la fiche pair."),
|
||||
("relay_hint_tip", "Il se peut qu'il ne doit pas possible de se connecter directement, vous pouvez essayer de vous connecter via un relais. \nEn outre, si vous souhaitez utiliser directement le relais, vous pouvez ajouter le suffixe \"/r\" à l'ID ou sélectionner l'option \"Toujours se connecter via le relais\" dans la fiche appareils distants."),
|
||||
("Reconnect", "Se reconnecter"),
|
||||
("Codec", "Codec"),
|
||||
("Resolution", "Résolution"),
|
||||
@ -470,14 +470,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Maximize", "Maximiser"),
|
||||
("Your Device", "Votre appareil"),
|
||||
("empty_recent_tip", "Oups, pas de sessions récentes!\nIl est temps d'en prévoir une nouvelle."),
|
||||
("empty_favorite_tip", "Vous n'avez pas encore de pairs favoris?\nTrouvons quelqu'un avec qui vous connecter et ajoutez-le à vos favoris!"),
|
||||
("empty_lan_tip", "Oh non, il semble que nous n'ayons pas encore de pairs découverts."),
|
||||
("empty_address_book_tip", "Ouh là là! il semble qu'il n'y ait actuellement aucun pair répertorié dans votre carnet d'adresses."),
|
||||
("empty_favorite_tip", "Vous n'avez pas encore d'appareils distants favorits?\nTrouvons quelqu'un avec qui vous connecter et ajoutez-la à vos favoris!"),
|
||||
("empty_lan_tip", "Oh non, il semble que nous n'ayons pas encore d'appareil réseaux local découverts."),
|
||||
("empty_address_book_tip", "Ouh là là! il semble qu'il n'y ait actuellement aucun appareil distant répertorié dans votre carnet d'adresses."),
|
||||
("eg: admin", "ex: admin"),
|
||||
("Empty Username", "Nom d'utilisation non spécifié"),
|
||||
("Empty Password", "Mot de passe non spécifié"),
|
||||
("Me", "Moi"),
|
||||
("identical_file_tip", "Ce fichier est identique à celui du pair."),
|
||||
("identical_file_tip", "Ce fichier est identique à celui de l'appareil distant."),
|
||||
("show_monitors_tip", "Afficher les moniteurs dans la barre d'outils"),
|
||||
("View Mode", "Mode vue"),
|
||||
("login_linux_tip", "Se connecter au compte Linux distant"),
|
||||
@ -498,8 +498,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Empreinte digitale"),
|
||||
("Copy Fingerprint", "Copier empreinte digitale"),
|
||||
("no fingerprints", "Pas d'empreintes digitales"),
|
||||
("Select a peer", "Sélectionnez la machine distante"),
|
||||
("Select peers", "Sélectionnez des machines distantes"),
|
||||
("Select a peer", "Sélectionnez l'appareil distant"),
|
||||
("Select peers", "Sélectionnez des appareils distants"),
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", "Désinstaller"),
|
||||
("Update", "Mise à jour"),
|
||||
@ -523,7 +523,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop", "Stopper"),
|
||||
("exceed_max_devices", "Vous avez atteint le nombre maximal d'appareils gérés."),
|
||||
("Sync with recent sessions", "Synchroniser avec les sessions récentes"),
|
||||
("Sort tags", "Trier les Tags"),
|
||||
("Sort tags", "Trier les étiquettes"),
|
||||
("Open connection in new tab", "Ouvrir la connexion dans un nouvel onglet"),
|
||||
("Move tab to new window", "Déplacer l'onglet vers une nouvelle fenêtre"),
|
||||
("Can not be empty", "Ne peux pas être vide"),
|
||||
@ -534,9 +534,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Grid View", "Vue Grille"),
|
||||
("List View", "Vue Liste"),
|
||||
("Select", "Sélectionner"),
|
||||
("Toggle Tags", "Basculer vers les Tags"),
|
||||
("Toggle Tags", "Basculer vers les étiquettes"),
|
||||
("pull_ab_failed_tip", "Impossible d'actualiser le carnet d'adresses"),
|
||||
("push_ab_failed_tip", "Échec de la synchronisation du carnet d'adresses"),
|
||||
("synced_peer_readded_tip", "Les appareils qui étaient présents dans les sessions récentes seront synchronisés avec le carnet d'adresses."),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", "Gagal memuat ulang buku alamat"),
|
||||
("push_ab_failed_tip", "Gagal menyinkronkan buku alamat ke server"),
|
||||
("synced_peer_readded_tip", "Perangkat yang terdaftar dalam sesi-sesi terbaru akan di-sinkronkan kembali ke buku alamat."),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", "Impossibile aggiornare la rubrica"),
|
||||
("push_ab_failed_tip", "Impossibile sincronizzare la rubrica con il server"),
|
||||
("synced_peer_readded_tip", "I dispositivi presenti nelle sessioni recenti saranno sincronizzati di nuovo nella rubrica."),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", "Невозможно обновить адресную книгу"),
|
||||
("push_ab_failed_tip", "Невозможно синхронизировать адресную книгу с сервером"),
|
||||
("synced_peer_readded_tip", "Устройства, присутствовавшие в последних сеансах, будут синхронизированы с адресной книгой."),
|
||||
("Change Color", "Изменить цвет"),
|
||||
("Primary Color", "Основной цвет"),
|
||||
("HSV Color", "HSV цвет"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", "未成功獲取地址簿"),
|
||||
("push_ab_failed_tip", "未成功上傳地址簿"),
|
||||
("synced_peer_readded_tip", "最近會話中存在的設備將會被重新同步到地址簿。"),
|
||||
("Change Color", "更改顏色"),
|
||||
("Primary Color", "基本色"),
|
||||
("HSV Color", "HSV 色"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -538,5 +538,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", ""),
|
||||
("Primary Color", ""),
|
||||
("HSV Color", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ async fn connect_and_login(
|
||||
return Ok(None);
|
||||
}
|
||||
Some(login_response::Union::PeerInfo(pi)) => {
|
||||
interface.handle_peer_info(pi, false);
|
||||
interface.handle_peer_info(pi);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
|
@ -39,7 +39,7 @@ use hbb_common::{
|
||||
tokio_util::codec::{BytesCodec, Framed},
|
||||
};
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
use scrap::android::call_main_service_mouse_input;
|
||||
use scrap::android::call_main_service_pointer_input;
|
||||
use serde_json::{json, value::Value};
|
||||
use sha2::{Digest, Sha256};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@ -1547,8 +1547,8 @@ impl Connection {
|
||||
match msg.union {
|
||||
Some(message::Union::MouseEvent(me)) => {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
if let Err(e) = call_main_service_mouse_input(me.mask, me.x, me.y) {
|
||||
log::debug!("call_main_service_mouse_input fail:{}", e);
|
||||
if let Err(e) = call_main_service_pointer_input("mouse", me.mask, me.x, me.y) {
|
||||
log::debug!("call_main_service_pointer_input fail:{}", e);
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if self.peer_keyboard_enabled() {
|
||||
@ -1560,8 +1560,35 @@ impl Connection {
|
||||
self.input_mouse(me, self.inner.id());
|
||||
}
|
||||
}
|
||||
Some(message::Union::PointerDeviceEvent(pde)) =>
|
||||
{
|
||||
Some(message::Union::PointerDeviceEvent(pde)) => {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
if let Err(e) = match pde.union {
|
||||
Some(pointer_device_event::Union::TouchEvent(touch)) => match touch.union {
|
||||
Some(touch_event::Union::PanStart(pan_start)) => {
|
||||
call_main_service_pointer_input(
|
||||
"touch",
|
||||
4,
|
||||
pan_start.x,
|
||||
pan_start.y,
|
||||
)
|
||||
}
|
||||
Some(touch_event::Union::PanUpdate(pan_update)) => {
|
||||
call_main_service_pointer_input(
|
||||
"touch",
|
||||
5,
|
||||
pan_update.x,
|
||||
pan_update.y,
|
||||
)
|
||||
}
|
||||
Some(touch_event::Union::PanEnd(pan_end)) => {
|
||||
call_main_service_pointer_input("touch", 6, pan_end.x, pan_end.y)
|
||||
}
|
||||
_ => Ok(()),
|
||||
},
|
||||
_ => Ok(()),
|
||||
} {
|
||||
log::debug!("call_main_service_pointer_input fail:{}", e);
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if self.peer_keyboard_enabled() {
|
||||
MOUSE_MOVE_TIME.store(get_time(), Ordering::SeqCst);
|
||||
|
@ -325,6 +325,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
|
||||
// for tmp use, without real conn id
|
||||
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
||||
#[cfg(windows)]
|
||||
let is_authorized = self.cm.is_authorized(self.conn_id);
|
||||
|
||||
#[cfg(windows)]
|
||||
|
@ -594,7 +594,7 @@ pub fn current_is_wayland() -> bool {
|
||||
|
||||
#[inline]
|
||||
pub fn get_new_version() -> String {
|
||||
hbb_common::get_version_from_url(&*SOFTWARE_UPDATE_URL.lock().unwrap())
|
||||
(*SOFTWARE_UPDATE_URL.lock().unwrap().rsplit('/').next().unwrap_or("")).to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -48,17 +48,6 @@ pub static IS_IN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15;
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[derive(Default)]
|
||||
pub struct CacheFlutter {
|
||||
pub pi: PeerInfo,
|
||||
pub sp: Option<SwitchDisplay>,
|
||||
pub cursor_data: HashMap<u64, CursorData>,
|
||||
pub cursor_id: u64,
|
||||
pub is_secured_direct: Option<(bool, bool)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Session<T: InvokeUiSession> {
|
||||
pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass
|
||||
@ -73,9 +62,6 @@ pub struct Session<T: InvokeUiSession> {
|
||||
pub server_file_transfer_enabled: Arc<RwLock<bool>>,
|
||||
pub server_clipboard_enabled: Arc<RwLock<bool>>,
|
||||
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub cache_flutter: Arc<RwLock<CacheFlutter>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -724,6 +710,49 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
send_pointer_device_event(evt, alt, ctrl, shift, command, self);
|
||||
}
|
||||
|
||||
pub fn send_touch_pan_event(
|
||||
&self,
|
||||
event: &str,
|
||||
x: i32,
|
||||
y: i32,
|
||||
alt: bool,
|
||||
ctrl: bool,
|
||||
shift: bool,
|
||||
command: bool,
|
||||
) {
|
||||
let mut touch_evt = TouchEvent::new();
|
||||
match event {
|
||||
"pan_start" => {
|
||||
touch_evt.set_pan_start(TouchPanStart {
|
||||
x,
|
||||
y,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
"pan_update" => {
|
||||
touch_evt.set_pan_update(TouchPanUpdate {
|
||||
x,
|
||||
y,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
"pan_end" => {
|
||||
touch_evt.set_pan_end(TouchPanEnd {
|
||||
x,
|
||||
y,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
log::warn!("unknown touch pan event: {}", event);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut evt = PointerDeviceEvent::new();
|
||||
evt.set_touch_event(touch_evt);
|
||||
send_pointer_device_event(evt, alt, ctrl, shift, command, self);
|
||||
}
|
||||
|
||||
pub fn send_mouse(
|
||||
&self,
|
||||
mask: i32,
|
||||
@ -1095,7 +1124,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
handle_login_error(self.lc.clone(), err, self)
|
||||
}
|
||||
|
||||
fn handle_peer_info(&mut self, mut pi: PeerInfo, is_cached_pi: bool) {
|
||||
fn handle_peer_info(&mut self, mut pi: PeerInfo) {
|
||||
log::debug!("handle_peer_info :{:?}", pi);
|
||||
pi.username = self.lc.read().unwrap().get_username(&pi);
|
||||
if pi.current_display as usize >= pi.displays.len() {
|
||||
@ -1116,12 +1145,10 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
self.msgbox("error", "Remote Error", "No Display", "");
|
||||
return;
|
||||
}
|
||||
if !is_cached_pi {
|
||||
self.try_change_init_resolution(pi.current_display);
|
||||
let p = self.lc.read().unwrap().should_auto_login();
|
||||
if !p.is_empty() {
|
||||
input_os_password(p, true, self.clone());
|
||||
}
|
||||
self.try_change_init_resolution(pi.current_display);
|
||||
let p = self.lc.read().unwrap().should_auto_login();
|
||||
if !p.is_empty() {
|
||||
input_os_password(p, true, self.clone());
|
||||
}
|
||||
let current = &pi.displays[pi.current_display as usize];
|
||||
self.set_display(
|
||||
@ -1222,23 +1249,6 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
pub fn ctrl_alt_del(&self) {
|
||||
self.send_key_event(&crate::keyboard::client::event_ctrl_alt_del());
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn restore_flutter_cache(&mut self) {
|
||||
if let Some((is_secured, direct)) = self.cache_flutter.read().unwrap().is_secured_direct {
|
||||
self.set_connection_type(is_secured, direct);
|
||||
}
|
||||
let pi = self.cache_flutter.read().unwrap().pi.clone();
|
||||
self.handle_peer_info(pi, true);
|
||||
if let Some(sp) = self.cache_flutter.read().unwrap().sp.as_ref() {
|
||||
self.handle_peer_switch_display(sp);
|
||||
}
|
||||
for (_, cd) in self.cache_flutter.read().unwrap().cursor_data.iter() {
|
||||
self.set_cursor_data(cd.clone());
|
||||
}
|
||||
self.set_cursor_id(self.cache_flutter.read().unwrap().cursor_id.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
|
Loading…
x
Reference in New Issue
Block a user