diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index 3ccf4988a..69f7df67b 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -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 }}' diff --git a/.github/workflows/flutter-tag.yml b/.github/workflows/flutter-tag.yml index 56ec2c43e..4925f26c8 100644 --- a/.github/workflows/flutter-tag.yml +++ b/.github/workflows/flutter-tag.yml @@ -15,4 +15,4 @@ jobs: secrets: inherit with: upload-artifact: true - upload-tag: "1.2.2" + upload-tag: "1.2.3" diff --git a/.github/workflows/history.yml b/.github/workflows/history.yml index 6e276162a..6921ea4cd 100644 --- a/.github/workflows/history.yml +++ b/.github/workflows/history.yml @@ -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: diff --git a/Cargo.lock b/Cargo.lock index 1dc94cfda..99784e400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5124,7 +5124,7 @@ dependencies = [ [[package]] name = "rustdesk" -version = "1.2.2" +version = "1.2.3" dependencies = [ "android_logger", "arboard", diff --git a/Cargo.toml b/Cargo.toml index f1e714e82..2abc198cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustdesk" -version = "1.2.2" +version = "1.2.3" authors = ["rustdesk "] edition = "2021" build= "build.rs" diff --git a/appimage/AppImageBuilder-aarch64.yml b/appimage/AppImageBuilder-aarch64.yml index b372f4eb9..337e022be 100644 --- a/appimage/AppImageBuilder-aarch64.yml +++ b/appimage/AppImageBuilder-aarch64.yml @@ -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: diff --git a/appimage/AppImageBuilder-x86_64.yml b/appimage/AppImageBuilder-x86_64.yml index 9a4054b62..650d2f201 100644 --- a/appimage/AppImageBuilder-x86_64.yml +++ b/appimage/AppImageBuilder-x86_64.yml @@ -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: diff --git a/flatpak/rustdesk.json b/flatpak/rustdesk.json index 4a8334fc9..4d2e297cc 100644 --- a/flatpak/rustdesk.json +++ b/flatpak/rustdesk.json @@ -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", diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt index 905a2734d..203558968 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt @@ -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) { diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt index 78e4e451e..535a3f8c3 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt @@ -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 -> { + } + } } } diff --git a/flutter/assets/auth-gitlab.svg b/flutter/assets/auth-gitlab.svg new file mode 100644 index 000000000..9402e1329 --- /dev/null +++ b/flutter/assets/auth-gitlab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index eb21ac821..48323d5fa 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -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 existing = const []}) { Map 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 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; diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 86a5b2c55..4af74e319 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -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(); diff --git a/flutter/lib/common/widgets/login.dart b/flutter/lib/common/widgets/login.dart index 7c17e9dea..b26397b94 100644 --- a/flutter/lib/common/widgets/login.dart +++ b/flutter/lib/common/widgets/login.dart @@ -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')), ), ), ], diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 889ba3fbe..a9d18b42c 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -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 deco) { + BuildContext context, Peer peer, Rx? 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, diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 120ea7ff1..3f4161477 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -426,7 +426,7 @@ class _PeerTabPageState extends State Widget selectionCount(int count) { return Align( alignment: Alignment.center, - child: Text('$count selected'), + child: Text('$count ${translate('Selected')}'), ); } diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index b4fd8e1d4..0e4898fc2 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -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; } } diff --git a/flutter/lib/common/widgets/remote_input.dart b/flutter/lib/common/widgets/remote_input.dart index 35eaf8a7c..b00cd1fb4 100644 --- a/flutter/lib/common/widgets/remote_input.dart +++ b/flutter/lib/common/widgets/remote_input.dart @@ -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; diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index b3ec3aa9d..7fcc7b3a7 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -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"; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index f158efb82..0222a286d 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -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'; diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index a417286ff..0d51cabdd 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.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 password: widget.password, switchUuid: widget.switchUuid, forceRelay: widget.forceRelay, + tabWindowId: widget.tabWindowId, ); WidgetsBinding.instance.addPostFrameCallback((_) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 3762a2b52..9c6ed6cec 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -55,6 +55,7 @@ class _ConnectionTabPageState extends State { 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 { 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 { 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 { 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 { .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 { )); } - 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(), diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 6a72fc3a1..9d6dec496 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -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 { diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index f5e217472..cbb7f7471 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -28,6 +28,7 @@ class AbModel { final pullError = "".obs; final pushError = "".obs; final tags = [].obs; + final RxMap tagColors = Map.fromEntries([]).obs; final peers = List.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 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 json = _jsonDecode(resp.body, resp.statusCode); + Map 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 = { + var m = _serialize(); + m.addAll({ "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 _jsonDecode(String body, int statusCode) { + Map _jsonDecodeResp(String body, int statusCode) { try { Map json = jsonDecode(body); return json; @@ -516,6 +505,50 @@ class AbModel { } } + Map _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 map = jsonDecode(data['tag_colors']); + tagColors.value = Map.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 future) { if (!shouldSyncAb()) return; Future.delayed(Duration.zero, () async { diff --git a/flutter/lib/models/desktop_render_texture.dart b/flutter/lib/models/desktop_render_texture.dart index 49864642c..f8456e339 100644 --- a/flutter/lib/models/desktop_render_texture.dart +++ b/flutter/lib/models/desktop_render_texture.dart @@ -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); diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index 6b50aa37f..971bbb7e5 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -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 toJson() { + return { + 'k': kind, + 'v': { + 't': type, + 'v': value, + } + }; + } +} + class InputModel { final WeakReference 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 getEvent(PointerEvent evt, String type) { + Map _getMouseEvent(PointerEvent evt, String type) { final Map 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 modify(Map evt) { + Map modify(Map 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 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 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 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 diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 6f68524f4..202216b59 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -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 = {}; final _waitForFirstImage = {}; final _constSessionId = Uuid().v4obj(); +class CachedPeerData { + Map updatePrivacyMode = {}; + Map peerInfo = {}; + List> cursorDataList = []; + Map 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 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 evt) async { + cachedPeerData.lastCursorId = evt; + await parent.target?.cursorModel.updateCursorId(evt); + } + + handleCursorData(Map evt) async { + cachedPeerData.cursorDataList.add(evt); + await parent.target?.cursorModel.updateCursorData(evt); + } + /// Handle the peer info synchronization event based on [evt]. handleSyncPeerInfo(Map evt, SessionID sessionId) async { if (evt['displays'] != null) { + cachedPeerData.peerInfo['displays'] = evt['displays']; List displays = json.decode(evt['displays']); List 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 { diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index 3f7d995b7..a8be78c74 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -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 newSessionWindow( WindowType type, String remoteId, String msg, List 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 _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 newSession( + Future 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 newRemoteDesktop( + Future newRemoteDesktop( String remoteId, { String? password, String? switchUuid, @@ -168,7 +177,7 @@ class RustDeskMultiWindowManager { ); } - Future newFileTransfer(String remoteId, + Future newFileTransfer(String remoteId, {String? password, bool? forceRelay}) async { return await newSession( WindowType.FileTransfer, @@ -180,7 +189,7 @@ class RustDeskMultiWindowManager { ); } - Future newPortForward(String remoteId, bool isRDP, + Future newPortForward(String remoteId, bool isRDP, {String? password, bool? forceRelay}) async { return await newSession( WindowType.PortForward, @@ -193,18 +202,22 @@ class RustDeskMultiWindowManager { ); } - Future call(WindowType type, String methodName, dynamic args) async { + Future 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 _findWindowsByType(WindowType type) { diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index fc0be7018..b7141455d 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -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 diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index e6862bc80..82206cbf2 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -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; } } diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 2cb0072c5..f40ff1463 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -1525,6 +1525,12 @@ pub struct Ab { pub peers: Vec, #[serde(default, deserialize_with = "deserialize_vec_string")] pub tags: Vec, + #[serde( + default, + deserialize_with = "deserialize_string", + skip_serializing_if = "String::is_empty" + )] + pub tag_colors: String, } impl Ab { diff --git a/libs/scrap/src/android/ffi.rs b/libs/scrap/src/android/ffi.rs index 6855fd3f6..e9c60ef93 100644 --- a/libs/scrap/src/android/ffi.rs +++ b/libs/scrap/src/android/ffi.rs @@ -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 { diff --git a/res/PKGBUILD b/res/PKGBUILD index 4d3911b3b..0e83b6891 100644 --- a/res/PKGBUILD +++ b/res/PKGBUILD @@ -1,5 +1,5 @@ pkgname=rustdesk -pkgver=1.2.2 +pkgver=1.2.3 pkgrel=0 epoch= pkgdesc="" diff --git a/res/rpm-flutter-suse.spec b/res/rpm-flutter-suse.spec index 08080424c..8c71fa7de 100644 --- a/res/rpm-flutter-suse.spec +++ b/res/rpm-flutter-suse.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.2.2 +Version: 1.2.3 Release: 0 Summary: RPM package License: GPL-3.0 diff --git a/res/rpm-flutter.spec b/res/rpm-flutter.spec index 5b4899bff..ca63093eb 100644 --- a/res/rpm-flutter.spec +++ b/res/rpm-flutter.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.2.2 +Version: 1.2.3 Release: 0 Summary: RPM package License: GPL-3.0 diff --git a/res/rpm.spec b/res/rpm.spec index 6ce788ae7..c92ad904a 100644 --- a/res/rpm.spec +++ b/res/rpm.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.2.2 +Version: 1.2.3 Release: 0 Summary: RPM package License: GPL-3.0 diff --git a/src/client.rs b/src/client.rs index 3ac6e8bb6..9e3479da8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -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, ""); } diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 9e78b17a5..aaf426e28 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -125,18 +125,7 @@ impl Remote { .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 Remote { } } 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 Remote { _ => {} }, 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 Remote { } } 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 Remote { } } 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); } _ => {} diff --git a/src/common.rs b/src/common.rs index 011ca0524..5ad92d914 100644 --- a/src/common.rs +++ b/src/common.rs @@ -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(()) } diff --git a/src/flutter.rs b/src/flutter.rs index 52190ce2e..af9580587 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -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 = 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::>(&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() {} diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 50f1e33dc..fb6fea40b 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -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::>(&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) { diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 739d5646b..baf6992b9 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -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(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 34ecdb276..8c20966c0 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -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(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index e32246da7..0e4d4db65 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -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(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 830cb0c45..c5540e75e 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -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(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 53a898a6a..fe1fe864f 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -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(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 46f816677..4e065c803 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -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(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 8d7f1c873..c77126729 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -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(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index aabbbfd27..7bb24ac53 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -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(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index eeb36af3b..35207642a 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -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(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 16e0585ee..f9263d572 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -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(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 2da6bd72e..722011788 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -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(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 45cb3f1c8..c8c343c5b 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -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(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 4febb9e00..22088a061 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -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(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 4c4d6e141..b1da5d94c 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -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(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 91e49be3a..a69b38e26 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -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(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 807b74ba4..ccca49561 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -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(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 0cd258b9e..873f5909a 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -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(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index c37e95c89..2be2d5da2 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -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(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 923dadc22..0391dd2f3 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -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(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 3eb850227..becb4c8ee 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -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(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 40360be5a..704b56d1e 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -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(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 50ae39902..2f248262b 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -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(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 5830525d9..cc5e97da8 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -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(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 4b7ced046..9711586d3 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -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(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 6b356a238..f5f4f95f2 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -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(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 5eb5c0fb4..e5d310ebb 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -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(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index f642bc124..480964e6e 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -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(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 76ea47cd4..cf94e7064 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -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(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 06591d147..c71a30342 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -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(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 6e21420fa..1ec47d26c 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -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(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 15aaac5f7..3c3a10da2 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -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(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 6d9d8089c..8ef8ca895 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -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(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index acdf8f106..6be8c93b2 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -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(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 8aeafd930..d83d9ef42 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -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(); } diff --git a/src/port_forward.rs b/src/port_forward.rs index 1e918cce1..6a087abe2 100644 --- a/src/port_forward.rs +++ b/src/port_forward.rs @@ -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; } _ => {} diff --git a/src/server/connection.rs b/src/server/connection.rs index e982e6a93..626dcb656 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -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); diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 29828d6b7..16fa59631 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -325,6 +325,7 @@ impl IpcTaskRunner { // for tmp use, without real conn id let mut write_jobs: Vec = Vec::new(); + #[cfg(windows)] let is_authorized = self.cm.is_authorized(self.conn_id); #[cfg(windows)] diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 7e1b3a9bb..ed2b4f4fc 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -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] diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 1fdff8144..a8304b5d0 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -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, - pub cursor_data: HashMap, - pub cursor_id: u64, - pub is_secured_direct: Option<(bool, bool)>, -} - #[derive(Clone, Default)] pub struct Session { pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass @@ -73,9 +62,6 @@ pub struct Session { pub server_file_transfer_enabled: Arc>, pub server_clipboard_enabled: Arc>, pub last_change_display: Arc>, - #[cfg(feature = "flutter")] - #[cfg(not(any(target_os = "android", target_os = "ios")))] - pub cache_flutter: Arc>, } #[derive(Clone)] @@ -724,6 +710,49 @@ impl Session { 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 Interface for Session { 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 Interface for Session { 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 Session { 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")]