From 8dba3942052b322b2772fb929821940e8437f239 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 1 Feb 2023 20:58:21 +0800 Subject: [PATCH 001/117] scale system cursor image Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_page.dart | 1 - .../lib/desktop/widgets/remote_menubar.dart | 37 +++++++++---------- flutter/lib/models/model.dart | 34 ++++++++--------- 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 2e4668159..1687f348e 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'dart:ui' as ui; import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_custom_cursor/cursor_manager.dart' diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 517dc9750..4f16f8227 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1133,26 +1133,23 @@ class _RemoteMenubarState extends State { }()); } - /// Show remote cursor scaling with image - if (widget.state.viewStyle.value != kRemoteViewStyleOriginal) { - displayMenu.add(() { - final opt = 'zoom-cursor'; - final state = PeerBoolOption.find(widget.id, opt); - return MenuEntrySwitch2( - switchType: SwitchType.scheckbox, - text: translate('Zoom cursor'), - getter: () { - return state; - }, - setter: (bool v) async { - state.value = v; - await bind.sessionToggleOption(id: widget.id, value: opt); - }, - padding: padding, - dismissOnClicked: true, - ); - }()); - } + displayMenu.add(() { + final opt = 'zoom-cursor'; + final state = PeerBoolOption.find(widget.id, opt); + return MenuEntrySwitch2( + switchType: SwitchType.scheckbox, + text: translate('Zoom cursor'), + getter: () { + return state; + }, + setter: (bool v) async { + state.value = v; + await bind.sessionToggleOption(id: widget.id, value: opt); + }, + padding: padding, + dismissOnClicked: true, + ); + }()); /// Show quality monitor displayMenu.add(MenuEntrySwitch( diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 1eac1be39..78e6ce6af 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -778,28 +778,24 @@ class CursorData { scale = 1.0; } else { // Update data if scale changed. - if (Platform.isWindows) { - final tgtWidth = (width * scale).toInt(); - final tgtHeight = (width * scale).toInt(); - if (tgtWidth < kMinCursorSize || tgtHeight < kMinCursorSize) { - double sw = kMinCursorSize.toDouble() / width; - double sh = kMinCursorSize.toDouble() / height; - scale = sw < sh ? sh : sw; - } + final tgtWidth = (width * scale).toInt(); + final tgtHeight = (width * scale).toInt(); + if (tgtWidth < kMinCursorSize || tgtHeight < kMinCursorSize) { + double sw = kMinCursorSize.toDouble() / width; + double sh = kMinCursorSize.toDouble() / height; + scale = sw < sh ? sh : sw; } } - if (Platform.isWindows) { - if (_doubleToInt(oldScale) != _doubleToInt(scale)) { - data = img2 - .copyResize( - image!, - width: (width * scale).toInt(), - height: (height * scale).toInt(), - interpolation: img2.Interpolation.average, - ) - .getBytes(format: img2.Format.bgra); - } + if (_doubleToInt(oldScale) != _doubleToInt(scale)) { + data = img2 + .copyResize( + image!, + width: (width * scale).toInt(), + height: (height * scale).toInt(), + interpolation: img2.Interpolation.average, + ) + .getBytes(format: img2.Format.bgra); } this.scale = scale; From 8881462f748068a6118196212c9f98aebe4e3a31 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 1 Feb 2023 22:12:28 +0800 Subject: [PATCH 002/117] debug macos Signed-off-by: fufesou --- flutter/lib/models/model.dart | 37 +++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 78e6ce6af..b2df5faac 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -747,7 +747,7 @@ class CanvasModel with ChangeNotifier { class CursorData { final String peerId; final int id; - final img2.Image? image; + final img2.Image image; double scale; Uint8List? data; final double hotxOrigin; @@ -788,14 +788,27 @@ class CursorData { } if (_doubleToInt(oldScale) != _doubleToInt(scale)) { - data = img2 - .copyResize( - image!, - width: (width * scale).toInt(), - height: (height * scale).toInt(), - interpolation: img2.Interpolation.average, - ) - .getBytes(format: img2.Format.bgra); + if (Platform.isWindows) { + data = img2 + .copyResize( + image, + width: (width * scale).toInt(), + height: (height * scale).toInt(), + interpolation: img2.Interpolation.average, + ) + .getBytes(format: img2.Format.bgra); + } else { + data = Uint8List.fromList( + img2.encodePng( + img2.copyResize( + image, + width: (width * scale).toInt(), + height: (height * scale).toInt(), + interpolation: img2.Interpolation.average, + ), + ), + ); + } } this.scale = scale; @@ -863,7 +876,7 @@ class PredefinedCursor { _cache = CursorData( peerId: '', id: id, - image: _image2?.clone(), + image: _image2!.clone(), scale: scale, data: data, hotxOrigin: @@ -1063,9 +1076,9 @@ class CursorModel with ChangeNotifier { Future _updateCache( Uint8List rgba, ui.Image image, int id, int w, int h) async { Uint8List? data; - img2.Image? imgOrigin; + img2.Image imgOrigin = + img2.Image.fromBytes(w, h, rgba, format: img2.Format.rgba); if (Platform.isWindows) { - imgOrigin = img2.Image.fromBytes(w, h, rgba, format: img2.Format.rgba); data = imgOrigin.getBytes(format: img2.Format.bgra); } else { ByteData? imgBytes = From b5fbc23cb9b9fc7ba915e1024085d8ac7ad1bf18 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 2 Feb 2023 12:39:39 +0800 Subject: [PATCH 003/117] zoom system cursor when view scale is adaptive Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_page.dart | 21 ++++++----- .../lib/desktop/widgets/remote_menubar.dart | 37 ++++++++++--------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 1687f348e..1ce9dec4a 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -362,10 +362,10 @@ class _RemotePageState extends State class ImagePaint extends StatefulWidget { final String id; - final Rx zoomCursor; - final Rx cursorOverImage; - final Rx keyboardEnabled; - final Rx remoteCursorMoved; + final RxBool zoomCursor; + final RxBool cursorOverImage; + final RxBool keyboardEnabled; + final RxBool remoteCursorMoved; final Widget Function(Widget)? listenerBuilder; ImagePaint( @@ -388,10 +388,10 @@ class _ImagePaintState extends State { final ScrollController _vertical = ScrollController(); String get id => widget.id; - Rx get zoomCursor => widget.zoomCursor; - Rx get cursorOverImage => widget.cursorOverImage; - Rx get keyboardEnabled => widget.keyboardEnabled; - Rx get remoteCursorMoved => widget.remoteCursorMoved; + RxBool get zoomCursor => widget.zoomCursor; + RxBool get cursorOverImage => widget.cursorOverImage; + RxBool get keyboardEnabled => widget.keyboardEnabled; + RxBool get remoteCursorMoved => widget.remoteCursorMoved; Widget Function(Widget)? get listenerBuilder => widget.listenerBuilder; @override @@ -466,7 +466,10 @@ class _ImagePaintState extends State { if (cache == null) { return MouseCursor.defer; } else { - final key = cache.updateGetKey(scale, zoomCursor.value); + final isViewAdaptive = + Provider.of(context, listen: false).viewStyle.style == + kRemoteViewStyleAdaptive; + final key = cache.updateGetKey(scale, zoomCursor.value && isViewAdaptive); if (!cursor.cachedKeys.contains(key)) { debugPrint("Register custom cursor with key $key"); // [Safety] diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 4f16f8227..517dc9750 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1133,23 +1133,26 @@ class _RemoteMenubarState extends State { }()); } - displayMenu.add(() { - final opt = 'zoom-cursor'; - final state = PeerBoolOption.find(widget.id, opt); - return MenuEntrySwitch2( - switchType: SwitchType.scheckbox, - text: translate('Zoom cursor'), - getter: () { - return state; - }, - setter: (bool v) async { - state.value = v; - await bind.sessionToggleOption(id: widget.id, value: opt); - }, - padding: padding, - dismissOnClicked: true, - ); - }()); + /// Show remote cursor scaling with image + if (widget.state.viewStyle.value != kRemoteViewStyleOriginal) { + displayMenu.add(() { + final opt = 'zoom-cursor'; + final state = PeerBoolOption.find(widget.id, opt); + return MenuEntrySwitch2( + switchType: SwitchType.scheckbox, + text: translate('Zoom cursor'), + getter: () { + return state; + }, + setter: (bool v) async { + state.value = v; + await bind.sessionToggleOption(id: widget.id, value: opt); + }, + padding: padding, + dismissOnClicked: true, + ); + }()); + } /// Show quality monitor displayMenu.add(MenuEntrySwitch( From 77ee60c8dc730af4b6658119b452cd1d417bba66 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 2 Feb 2023 12:47:39 +0800 Subject: [PATCH 004/117] scale remote cursor when view style is adaptive Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 1ce9dec4a..858157853 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -635,7 +635,8 @@ class CursorPaint extends StatelessWidget { double x = (m.x - hotx) * c.scale + cx; double y = (m.y - hoty) * c.scale + cy; double scale = 1.0; - if (zoomCursor.isTrue) { + final isViewAdaptive = c.viewStyle.style == kRemoteViewStyleAdaptive; + if (zoomCursor.isTrue && isViewAdaptive) { x = m.x - hotx + cx / c.scale; y = m.y - hoty + cy / c.scale; scale = c.scale; From aafc2e0a8e4d0525b19ad011936354d6802bfb84 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 2 Feb 2023 13:08:41 +0800 Subject: [PATCH 005/117] zoom cursor on different OSs Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_page.dart | 28 +++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 858157853..dd71797f3 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -466,10 +466,19 @@ class _ImagePaintState extends State { if (cache == null) { return MouseCursor.defer; } else { - final isViewAdaptive = - Provider.of(context, listen: false).viewStyle.style == - kRemoteViewStyleAdaptive; - final key = cache.updateGetKey(scale, zoomCursor.value && isViewAdaptive); + bool shouldScale = false; + if (Platform.isWindows) { + final isViewAdaptive = + Provider.of(context, listen: false).viewStyle.style == + kRemoteViewStyleAdaptive; + shouldScale = zoomCursor.value && isViewAdaptive; + } else { + final isViewOriginal = + Provider.of(context, listen: false).viewStyle.style == + kRemoteViewStyleOriginal; + shouldScale = zoomCursor.value || isViewOriginal; + } + final key = cache.updateGetKey(scale, shouldScale); if (!cursor.cachedKeys.contains(key)) { debugPrint("Register custom cursor with key $key"); // [Safety] @@ -635,8 +644,15 @@ class CursorPaint extends StatelessWidget { double x = (m.x - hotx) * c.scale + cx; double y = (m.y - hoty) * c.scale + cy; double scale = 1.0; - final isViewAdaptive = c.viewStyle.style == kRemoteViewStyleAdaptive; - if (zoomCursor.isTrue && isViewAdaptive) { + bool shouldScale = false; + if (Platform.isWindows) { + final isViewAdaptive = c.viewStyle.style == kRemoteViewStyleAdaptive; + shouldScale = zoomCursor.value && isViewAdaptive; + } else { + final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal; + shouldScale = zoomCursor.value || isViewOriginal; + } + if (shouldScale) { x = m.x - hotx + cx / c.scale; y = m.y - hoty + cy / c.scale; scale = c.scale; From d511d1e27a024847a50d31ec85392478907dccd6 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 2 Feb 2023 13:40:13 +0800 Subject: [PATCH 006/117] zoom remote cursor when view style is original Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_page.dart | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index dd71797f3..f38cdfb6e 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -644,15 +644,8 @@ class CursorPaint extends StatelessWidget { double x = (m.x - hotx) * c.scale + cx; double y = (m.y - hoty) * c.scale + cy; double scale = 1.0; - bool shouldScale = false; - if (Platform.isWindows) { - final isViewAdaptive = c.viewStyle.style == kRemoteViewStyleAdaptive; - shouldScale = zoomCursor.value && isViewAdaptive; - } else { - final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal; - shouldScale = zoomCursor.value || isViewOriginal; - } - if (shouldScale) { + final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal; + if (zoomCursor.value || isViewOriginal) { x = m.x - hotx + cx / c.scale; y = m.y - hoty + cy / c.scale; scale = c.scale; From f9e3a3f074ffc3cfc43e398abb712d631497e930 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 2 Feb 2023 14:39:58 +0800 Subject: [PATCH 007/117] zoom cursor with dpi Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_page.dart | 32 ++++++++++++---------- flutter/lib/models/model.dart | 15 +++++----- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index f38cdfb6e..f7889d008 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -399,6 +399,20 @@ class _ImagePaintState extends State { final m = Provider.of(context); var c = Provider.of(context); final s = c.scale; + var cursorScale = 1.0; + + if (Platform.isWindows) { + // debug win10 + final isViewAdaptive = c.viewStyle.style == kRemoteViewStyleAdaptive; + if (zoomCursor.value && isViewAdaptive) { + cursorScale = s * c.devicePixelRatio; + } + } else { + final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal; + if (zoomCursor.value || isViewOriginal) { + cursorScale = s; + } + } mouseRegion({child}) => Obx(() => MouseRegion( cursor: cursorOverImage.isTrue @@ -414,10 +428,10 @@ class _ImagePaintState extends State { _lastRemoteCursorMoved = false; _firstEnterImage.value = true; } - return _buildCustomCursor(context, s); + return _buildCustomCursor(context, cursorScale); } }()) - : _buildDisabledCursor(context, s) + : _buildDisabledCursor(context, cursorScale) : MouseCursor.defer, onHover: (evt) {}, child: child)); @@ -466,19 +480,7 @@ class _ImagePaintState extends State { if (cache == null) { return MouseCursor.defer; } else { - bool shouldScale = false; - if (Platform.isWindows) { - final isViewAdaptive = - Provider.of(context, listen: false).viewStyle.style == - kRemoteViewStyleAdaptive; - shouldScale = zoomCursor.value && isViewAdaptive; - } else { - final isViewOriginal = - Provider.of(context, listen: false).viewStyle.style == - kRemoteViewStyleOriginal; - shouldScale = zoomCursor.value || isViewOriginal; - } - final key = cache.updateGetKey(scale, shouldScale); + final key = cache.updateGetKey(scale); if (!cursor.cachedKeys.contains(key)) { debugPrint("Register custom cursor with key $key"); // [Safety] diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index b2df5faac..f49bb270c 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -540,6 +540,7 @@ class CanvasModel with ChangeNotifier { double _y = 0; // image scale double _scale = 1.0; + double _devicePixelRatio = 1.0; Size _size = Size.zero; // the tabbar over the image // double tabBarHeight = 0.0; @@ -563,6 +564,7 @@ class CanvasModel with ChangeNotifier { double get x => _x; double get y => _y; double get scale => _scale; + double get devicePixelRatio => _devicePixelRatio; Size get size => _size; ScrollStyle get scrollStyle => _scrollStyle; ViewStyle get viewStyle => _lastViewStyle; @@ -611,8 +613,9 @@ class CanvasModel with ChangeNotifier { _lastViewStyle = viewStyle; _scale = viewStyle.scale; + _devicePixelRatio = ui.window.devicePixelRatio; if (kIgnoreDpi && style == kRemoteViewStyleOriginal) { - _scale = 1.0 / ui.window.devicePixelRatio; + _scale = 1.0 / _devicePixelRatio; } _x = (size.width - displayWidth * _scale) / 2; _y = (size.height - displayHeight * _scale) / 2; @@ -772,11 +775,9 @@ class CursorData { int _doubleToInt(double v) => (v * 10e6).round().toInt(); - double _checkUpdateScale(double scale, bool shouldScale) { + double _checkUpdateScale(double scale) { double oldScale = this.scale; - if (!shouldScale) { - scale = 1.0; - } else { + if (scale != 1.0) { // Update data if scale changed. final tgtWidth = (width * scale).toInt(); final tgtHeight = (width * scale).toInt(); @@ -817,8 +818,8 @@ class CursorData { return scale; } - String updateGetKey(double scale, bool shouldScale) { - scale = _checkUpdateScale(scale, shouldScale); + String updateGetKey(double scale) { + scale = _checkUpdateScale(scale); return '${peerId}_${id}_${_doubleToInt(width * scale)}_${_doubleToInt(height * scale)}'; } } From 0d9d506dac843986685c69ca72fdfd553d217f78 Mon Sep 17 00:00:00 2001 From: NicKoehler <53040044+NicKoehler@users.noreply.github.com> Date: Thu, 2 Feb 2023 14:48:22 +0100 Subject: [PATCH 008/117] Update it.rs --- src/lang/it.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index 6edd4a461..d84b56a8a 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -436,14 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Switch Sides", "Cambia lato"), ("Please confirm if you want to share your desktop?", "Vuoi condividere il tuo desktop?"), ("Closed as expected", "Chiuso come previsto"), - ("Display", ""), - ("Default View Style", ""), - ("Default Scroll Style", ""), - ("Default Image Quality", ""), - ("Default Codec", ""), - ("Bitrate", ""), - ("FPS", ""), - ("Auto", ""), - ("Other Default Options", ""), + ("Display", "Visualizzazione"), + ("Default View Style", "Stile Visualizzazione Predefinito"), + ("Default Scroll Style", "Stile Scorrimento Predefinito"), + ("Default Image Quality", "Qualità Immagine Predefinita"), + ("Default Codec", "Codec Predefinito"), + ("Bitrate", "Bitrate"), + ("FPS", "FPS"), + ("Auto", "Auto"), + ("Other Default Options", "Altre Opzioni Predefinite"), ].iter().cloned().collect(); } From 1a1bd1b5d8c6be911451e288b577092795d967f4 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 2 Feb 2023 16:45:29 +0800 Subject: [PATCH 009/117] recover reordered peer tab, because flutter 3.7.0 fix ReorderableListView crash Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_tab_page.dart | 235 +++++++++++++----- 1 file changed, 168 insertions(+), 67 deletions(-) diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 278f5861c..fff5e2ffd 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:ui' as ui; import 'package:bot_toast/bot_toast.dart'; @@ -20,7 +21,9 @@ const int groupTabIndex = 4; const String defaultGroupTabname = 'Group'; class StatePeerTab { - final RxInt currentTab = 0.obs; + final RxInt currentTab = 0.obs; // index in tabNames + final RxList visibleOrderedTabs = RxList.empty(growable: true); + List tabOrder = List.from([0, 1, 2, 3, 4]); // constant length final RxInt tabHiddenFlag = 0.obs; final RxList tabNames = [ 'Recent Sessions', @@ -31,53 +34,80 @@ class StatePeerTab { ].obs; StatePeerTab._() { + // init tabHiddenFlag tabHiddenFlag.value = (int.tryParse( bind.getLocalFlutterConfig(k: 'hidden-peer-card'), radix: 2) ?? 0); var tabs = _notHiddenTabs(); + // remove dynamic tabs + tabs.remove(groupTabIndex); + // init tabOrder + try { + final conf = bind.getLocalFlutterConfig(k: 'peer-tab-order'); + if (conf.isNotEmpty) { + final json = jsonDecode(conf); + if (json is List) { + final List list = + json.map((e) => int.tryParse(e.toString()) ?? -1).toList(); + if (list.length == tabOrder.length && + tabOrder.every((e) => list.contains(e))) { + tabOrder = list; + } + } + } + } catch (e) { + debugPrintStack(label: '$e'); + } + // init visibleOrderedTabs + var tempList = tabOrder.toList(); + tempList.removeWhere((e) => !tabs.contains(e)); + visibleOrderedTabs.value = tempList; + // init currentTab currentTab.value = int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0; if (!tabs.contains(currentTab.value)) { - currentTab.value = 0; + if (tabs.isNotEmpty) { + currentTab.value = tabs[0]; + } else { + currentTab.value = 0; + } } } static final StatePeerTab instance = StatePeerTab._(); + // check dynamic tabs check() { - var tabs = _notHiddenTabs(); - if (filterGroupCard()) { - if (currentTab.value == groupTabIndex) { - currentTab.value = - tabs.firstWhereOrNull((e) => e != groupTabIndex) ?? 0; - bind.setLocalFlutterConfig( - k: 'peer-tab-index', v: currentTab.value.toString()); - } + tabOrder2visibleOrderedTabs(); + if (visibleOrderedTabs.contains(groupTabIndex) && + int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) == + groupTabIndex) { + currentTab.value = groupTabIndex; + } + if (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isNotEmpty) { + tabNames[groupTabIndex] = gFFI.userModel.groupName.value; } else { - if (gFFI.userModel.isAdmin.isFalse && - gFFI.userModel.groupName.isNotEmpty) { - tabNames[groupTabIndex] = gFFI.userModel.groupName.value; - } else { - tabNames[groupTabIndex] = defaultGroupTabname; - } - if (tabs.contains(groupTabIndex) && - int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) == - groupTabIndex) { - currentTab.value = groupTabIndex; - } + tabNames[groupTabIndex] = defaultGroupTabname; } } - List currentTabs() { - var v = List.empty(growable: true); - for (int i = 0; i < tabNames.length; i++) { - if (!_isTabHidden(i) && !_isTabFilter(i)) { - v.add(i); - } + visibleOrderedTabs2TabOrder() { + var tmpTabOrder = visibleOrderedTabs.toList(); + var left = tabOrder.where((e) => !tmpTabOrder.contains(e)).toList(); + for (var t in left) { + _addTabInOrder(tmpTabOrder, t); } - return v; + statePeerTab.tabOrder = tmpTabOrder; + bind.setLocalFlutterConfig(k: 'peer-tab-order', v: jsonEncode(tmpTabOrder)); } + tabOrder2visibleOrderedTabs() { + var visible = statePeerTab.visibleTabs(); + statePeerTab.visibleOrderedTabs.value = + statePeerTab.tabOrder.where((e) => visible.contains(e)).toList(); + } + + // return true if hide group card bool filterGroupCard() { if (gFFI.groupModel.users.isEmpty || (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) { @@ -87,6 +117,17 @@ class StatePeerTab { } } + // return index array of tabNames + List visibleTabs() { + var v = List.empty(growable: true); + for (int i = 0; i < tabNames.length; i++) { + if (!_isTabHidden(i) && !_isTabFilter(i)) { + v.add(i); + } + } + return v; + } + bool _isTabHidden(int tabindex) { return tabHiddenFlag & (1 << tabindex) != 0; } @@ -107,6 +148,41 @@ class StatePeerTab { } return v; } + + // add tabIndex to list + _addTabInOrder(List list, int tabIndex) { + if (!tabOrder.contains(tabIndex) || list.contains(tabIndex)) { + return; + } + bool sameOrder = true; + int lastIndex = -1; + for (int i = 0; i < list.length; i++) { + var index = tabOrder.lastIndexOf(list[i]); + if (index > lastIndex) { + lastIndex = index; + continue; + } else { + sameOrder = false; + break; + } + } + if (sameOrder) { + var indexInTabOrder = tabOrder.indexOf(tabIndex); + var left = List.empty(growable: true); + for (int i = 0; i < indexInTabOrder; i++) { + left.add(tabOrder[i]); + } + int insertIndex = list.lastIndexWhere((e) => left.contains(e)); + if (insertIndex < 0) { + insertIndex = 0; + } else { + insertIndex += 1; + } + list.insert(insertIndex, tabIndex); + } else { + list.add(tabIndex); + } + } } final statePeerTab = StatePeerTab.instance; @@ -177,11 +253,6 @@ class _PeerTabPageState extends State } } - @override - void dispose() { - super.dispose(); - } - @override Widget build(BuildContext context) { return Column( @@ -215,40 +286,57 @@ class _PeerTabPageState extends State Widget _createSwitchBar(BuildContext context) { final textColor = Theme.of(context).textTheme.titleLarge?.color; return Obx(() { - var tabs = statePeerTab.currentTabs(); - return ListView( + var tabs = statePeerTab.visibleOrderedTabs; + int indexCounter = -1; + return ReorderableListView( + buildDefaultDragHandles: false, + onReorder: (oldIndex, newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + var list = tabs.toList(); + final int item = list.removeAt(oldIndex); + list.insert(newIndex, item); + tabs.value = list; + statePeerTab.visibleOrderedTabs2TabOrder(); + }, scrollDirection: Axis.horizontal, physics: NeverScrollableScrollPhysics(), - controller: ScrollController(), + scrollController: ScrollController(), children: tabs.map((t) { - return InkWell( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: statePeerTab.currentTab.value == t - ? Theme.of(context).backgroundColor - : null, - borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), - ), - child: Align( - alignment: Alignment.center, - child: Text( - translatedTabname(t), - textAlign: TextAlign.center, - style: TextStyle( - height: 1, - fontSize: 14, - color: statePeerTab.currentTab.value == t - ? textColor - : textColor - ?..withOpacity(0.5)), + indexCounter++; + return ReorderableDragStartListener( + key: ValueKey(t), + index: indexCounter, + child: InkWell( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: statePeerTab.currentTab.value == t + ? Theme.of(context).backgroundColor + : null, + borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), ), - )), - onTap: () async { - await handleTabSelection(t); - await bind.setLocalFlutterConfig( - k: 'peer-tab-index', v: t.toString()); - }, + child: Align( + alignment: Alignment.center, + child: Text( + translatedTabname(t), + textAlign: TextAlign.center, + style: TextStyle( + height: 1, + fontSize: 14, + color: statePeerTab.currentTab.value == t + ? textColor + : textColor + ?..withOpacity(0.5)), + ), + )), + onTap: () async { + await handleTabSelection(t); + await bind.setLocalFlutterConfig( + k: 'peer-tab-index', v: t.toString()); + }, + ), ); }).toList()); }); @@ -275,7 +363,7 @@ class _PeerTabPageState extends State final verticalMargin = isDesktop ? 12.0 : 6.0; return Expanded( child: Obx(() { - var tabs = statePeerTab.currentTabs(); + var tabs = statePeerTab.visibleOrderedTabs; if (tabs.isEmpty) { return visibleContextMenuListener(Center( child: Text(translate('Right click to select tabs')), @@ -322,7 +410,7 @@ class _PeerTabPageState extends State } adjustTab() { - var tabs = statePeerTab.currentTabs(); + var tabs = statePeerTab.visibleOrderedTabs; if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) { statePeerTab.currentTab.value = tabs[0]; } @@ -349,11 +437,13 @@ class _PeerTabPageState extends State Widget visibleContextMenu(CancelFunc cancelFunc) { return Obx(() { final List menu = List.empty(growable: true); + final List menuIndex = List.empty(growable: true); for (int i = 0; i < statePeerTab.tabNames.length; i++) { if (i == groupTabIndex && statePeerTab.filterGroupCard()) { continue; } int bitMask = 1 << i; + menuIndex.add(i); menu.add(MenuEntrySwitch( switchType: SwitchType.scheckbox, text: translatedTabname(i), @@ -369,12 +459,21 @@ class _PeerTabPageState extends State await bind.setLocalFlutterConfig( k: 'hidden-peer-card', v: statePeerTab.tabHiddenFlag.value.toRadixString(2)); + statePeerTab.tabOrder2visibleOrderedTabs(); cancelFunc(); adjustTab(); })); } + // show in tabOrder + List menu2 = List.empty(growable: true); + statePeerTab.tabOrder.map((e) { + final index = menuIndex.indexOf(e); + if (index >= 0) { + menu2.add(menu[index]); + } + }).toList(); return mod_menu.PopupMenu( - items: menu + items: menu2 .map((entry) => entry.build( context, const MenuConfig( @@ -421,7 +520,9 @@ class _PeerSearchBarState extends State { FocusNode focusNode = FocusNode(); focusNode.addListener(() { focused.value = focusNode.hasFocus; - peerSearchTextController.selection = TextSelection(baseOffset: 0, extentOffset: peerSearchTextController.value.text.length); + peerSearchTextController.selection = TextSelection( + baseOffset: 0, + extentOffset: peerSearchTextController.value.text.length); }); return Container( width: 120, From 50c8855d2816897527fb65c4cd01c8e1f7f16c6a Mon Sep 17 00:00:00 2001 From: rustdesk Date: Fri, 3 Feb 2023 16:18:08 +0800 Subject: [PATCH 010/117] unify peer tab text color with tab bar text color --- flutter/lib/common/widgets/peer_tab_page.dart | 5 ++--- flutter/pubspec.lock | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index fff5e2ffd..150121c59 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -284,7 +284,6 @@ class _PeerTabPageState extends State } Widget _createSwitchBar(BuildContext context) { - final textColor = Theme.of(context).textTheme.titleLarge?.color; return Obx(() { var tabs = statePeerTab.visibleOrderedTabs; int indexCounter = -1; @@ -326,8 +325,8 @@ class _PeerTabPageState extends State height: 1, fontSize: 14, color: statePeerTab.currentTab.value == t - ? textColor - : textColor + ? MyTheme.tabbar(context).selectedTextColor + : MyTheme.tabbar(context).unSelectedTextColor ?..withOpacity(0.5)), ), )), diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index c193c0651..ebb105178 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -475,10 +475,10 @@ packages: dependency: "direct main" description: name: flutter_custom_cursor - sha256: "6c5204cf6a16650355b8aa47a8402e79922c07641390a32021a1069b561909ec" + sha256: "3850a32ac6de351ccc5e4286b6d94ff70c10abecd44479ea6c5aaea17264285d" url: "https://pub.dev" source: hosted - version: "0.0.3" + version: "0.0.4" flutter_improved_scrolling: dependency: "direct main" description: From e05b95743c2f67bfaee077f4338007c9c6e16238 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 3 Feb 2023 17:02:28 +0800 Subject: [PATCH 011/117] cursor position and size update Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_page.dart | 77 +++++++++++-------- .../widgets/material_mod_popup_menu.dart | 16 +++- .../lib/desktop/widgets/remote_menubar.dart | 22 ++++-- flutter/lib/models/input_model.dart | 2 - flutter/lib/models/model.dart | 2 +- 5 files changed, 74 insertions(+), 45 deletions(-) diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index f7889d008..0e0127312 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -399,42 +399,51 @@ class _ImagePaintState extends State { final m = Provider.of(context); var c = Provider.of(context); final s = c.scale; - var cursorScale = 1.0; - if (Platform.isWindows) { - // debug win10 - final isViewAdaptive = c.viewStyle.style == kRemoteViewStyleAdaptive; - if (zoomCursor.value && isViewAdaptive) { - cursorScale = s * c.devicePixelRatio; - } - } else { - final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal; - if (zoomCursor.value || isViewOriginal) { - cursorScale = s; - } - } + mouseRegion({child}) => Obx(() { + double getCursorScale() { + var c = Provider.of(context); + var cursorScale = 1.0; + if (Platform.isWindows) { + // debug win10 + final isViewAdaptive = + c.viewStyle.style == kRemoteViewStyleAdaptive; + if (zoomCursor.value && isViewAdaptive) { + cursorScale = s * c.devicePixelRatio; + } + } else { + final isViewOriginal = + c.viewStyle.style == kRemoteViewStyleOriginal; + if (zoomCursor.value || isViewOriginal) { + cursorScale = s; + } + } + return cursorScale; + } - mouseRegion({child}) => Obx(() => MouseRegion( - cursor: cursorOverImage.isTrue - ? c.cursorEmbedded - ? SystemMouseCursors.none - : keyboardEnabled.isTrue - ? (() { - if (remoteCursorMoved.isTrue) { - _lastRemoteCursorMoved = true; - return SystemMouseCursors.none; - } else { - if (_lastRemoteCursorMoved) { - _lastRemoteCursorMoved = false; - _firstEnterImage.value = true; - } - return _buildCustomCursor(context, cursorScale); - } - }()) - : _buildDisabledCursor(context, cursorScale) - : MouseCursor.defer, - onHover: (evt) {}, - child: child)); + return MouseRegion( + cursor: cursorOverImage.isTrue + ? c.cursorEmbedded + ? SystemMouseCursors.none + : keyboardEnabled.isTrue + ? (() { + if (remoteCursorMoved.isTrue) { + _lastRemoteCursorMoved = true; + return SystemMouseCursors.none; + } else { + if (_lastRemoteCursorMoved) { + _lastRemoteCursorMoved = false; + _firstEnterImage.value = true; + } + return _buildCustomCursor( + context, getCursorScale()); + } + }()) + : _buildDisabledCursor(context, getCursorScale()) + : MouseCursor.defer, + onHover: (evt) {}, + child: child); + }); if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) { final imageWidth = c.getDisplayWidth() * s; diff --git a/flutter/lib/desktop/widgets/material_mod_popup_menu.dart b/flutter/lib/desktop/widgets/material_mod_popup_menu.dart index a371e8f52..666c9a6e2 100644 --- a/flutter/lib/desktop/widgets/material_mod_popup_menu.dart +++ b/flutter/lib/desktop/widgets/material_mod_popup_menu.dart @@ -790,6 +790,7 @@ class _PopupMenuRoute extends PopupRoute { _PopupMenuRoute({ required this.position, required this.items, + this.menuWrapper, this.initialValue, this.elevation, required this.barrierLabel, @@ -802,6 +803,7 @@ class _PopupMenuRoute extends PopupRoute { final RelativeRect position; final List> items; + final MenuWrapper? menuWrapper; final List itemSizes; final T? initialValue; final double? elevation; @@ -844,11 +846,14 @@ class _PopupMenuRoute extends PopupRoute { } } - final Widget menu = _PopupMenu( + Widget menu = _PopupMenu( route: this, semanticLabel: semanticLabel, constraints: constraints, ); + if (this.menuWrapper != null) { + menu = this.menuWrapper!(menu); + } final MediaQueryData mediaQuery = MediaQuery.of(context); return MediaQuery.removePadding( context: context, @@ -1035,6 +1040,7 @@ Future showMenu({ required BuildContext context, required RelativeRect position, required List> items, + MenuWrapper? menuWrapper, T? initialValue, double? elevation, String? semanticLabel, @@ -1062,6 +1068,7 @@ Future showMenu({ return navigator.push(_PopupMenuRoute( position: position, items: items, + menuWrapper: menuWrapper, initialValue: initialValue, elevation: elevation, semanticLabel: semanticLabel, @@ -1094,6 +1101,8 @@ typedef PopupMenuCanceled = void Function(); typedef PopupMenuItemBuilder = List> Function( BuildContext context); +typedef MenuWrapper = Widget Function(Widget child); + /// Displays a menu when pressed and calls [onSelected] when the menu is dismissed /// because an item was selected. The value passed to [onSelected] is the value of /// the selected menu item. @@ -1124,6 +1133,7 @@ class PopupMenuButton extends StatefulWidget { const PopupMenuButton({ Key? key, required this.itemBuilder, + this.menuWrapper, this.initialValue, this.onHover, this.onSelected, @@ -1151,6 +1161,9 @@ class PopupMenuButton extends StatefulWidget { /// Called when the button is pressed to create the items to show in the menu. final PopupMenuItemBuilder itemBuilder; + /// Menu wrapper. + final MenuWrapper? menuWrapper; + /// The value of the menu item, if any, that should be highlighted when the menu opens. final T? initialValue; @@ -1333,6 +1346,7 @@ class PopupMenuButtonState extends State> { context: context, elevation: widget.elevation ?? popupMenuTheme.elevation, items: items, + menuWrapper: widget.menuWrapper, initialValue: widget.initialValue, position: position, shape: widget.shape ?? popupMenuTheme.shape, diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 64d289fcc..db1721d99 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -221,6 +221,16 @@ class _RemoteMenubarState extends State { } } + Widget _buildPointerTrackWidget(Widget child) { + return Listener( + onPointerHover: (PointerHoverEvent e) => + widget.ffi.inputModel.lastMousePos = e.position, + child: MouseRegion( + child: child, + ), + ); + } + Widget _buildMenubar(BuildContext context) { final List menubarItems = []; if (!isWebDesktop) { @@ -379,13 +389,10 @@ class _RemoteMenubarState extends State { mod_menu.PopupMenuItem( height: _MenubarTheme.height, padding: EdgeInsets.zero, - child: Listener( - onPointerHover: (PointerHoverEvent e) => - widget.ffi.inputModel.lastMousePos = e.position, - child: MouseRegion( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: rowChildren), + child: _buildPointerTrackWidget( + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: rowChildren, ), ), ) @@ -435,6 +442,7 @@ class _RemoteMenubarState extends State { ), tooltip: translate('Display Settings'), position: mod_menu.PopupMenuPosition.under, + menuWrapper: _buildPointerTrackWidget, itemBuilder: (BuildContext context) => _getDisplayMenu(snapshot.data!, remoteCount) .map((entry) => entry.build( diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index d2f671cdc..8c37f50bd 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -310,7 +310,6 @@ class InputModel { } } - int _signOrZero(num x) { if (x == 0) { return 0; @@ -362,7 +361,6 @@ class InputModel { trackpadScrollDistance = Offset.zero; } - void onPointDownImage(PointerDownEvent e) { debugPrint("onPointDownImage"); if (e.kind != ui.PointerDeviceKind.mouse) { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index f49bb270c..da711bf13 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -244,7 +244,6 @@ class FfiModel with ChangeNotifier { parent.target?.canvasModel.updateViewStyle(); } parent.target?.recordingModel.onSwitchDisplay(); - parent.target?.inputModel.refreshMousePos(); notifyListeners(); } @@ -621,6 +620,7 @@ class CanvasModel with ChangeNotifier { _y = (size.height - displayHeight * _scale) / 2; _imageOverflow.value = _x < 0 || y < 0; notifyListeners(); + parent.target?.inputModel.refreshMousePos(); } updateScrollStyle() async { From 17aac13247a94bbf35a8a169a4b50f8a4b14a9f4 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 3 Feb 2023 18:28:47 +0800 Subject: [PATCH 012/117] ignore first update cursor postion Signed-off-by: fufesou --- flutter/lib/desktop/widgets/remote_menubar.dart | 3 ++- flutter/lib/models/model.dart | 7 ++++++- src/client.rs | 8 ++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index db1721d99..2a84dcf14 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1157,8 +1157,9 @@ class _RemoteMenubarState extends State { return state; }, setter: (bool v) async { - state.value = v; await bind.sessionToggleOption(id: widget.id, value: opt); + state.value = + bind.sessionGetToggleOptionSync(id: widget.id, arg: opt); }, padding: padding, dismissOnClicked: true, diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index da711bf13..8a7a1005d 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -904,6 +904,7 @@ class CursorModel with ChangeNotifier { double _hoty = 0; double _displayOriginX = 0; double _displayOriginY = 0; + bool _firstUpdateMousePos = false; bool gotMouseControl = true; DateTime _lastPeerMouse = DateTime.now() .subtract(Duration(milliseconds: 2 * kMouseControlTimeoutMSec)); @@ -1121,7 +1122,11 @@ class CursorModel with ChangeNotifier { /// Update the cursor position. updateCursorPosition(Map evt, String id) async { - gotMouseControl = false; + if (!_firstUpdateMousePos) { + _firstUpdateMousePos = true; + } else { + gotMouseControl = false; + } _lastPeerMouse = DateTime.now(); _x = double.parse(evt['x']); _y = double.parse(evt['y']); diff --git a/src/client.rs b/src/client.rs index fb42ce840..e0ac68c5d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1117,8 +1117,12 @@ impl LoginConfigHandler { } else if name == "show-quality-monitor" { config.show_quality_monitor.v = !config.show_quality_monitor.v; } else { - let v = self.options.get(&name).is_some(); - if v { + let is_set = self + .options + .get(&name) + .map(|o| !o.is_empty()) + .unwrap_or(false); + if is_set { self.config.options.remove(&name); } else { self.config.options.insert(name, "Y".to_owned()); From 66851efaa3ca2b9c5274ed80b7e43c155d4ff789 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Fri, 3 Feb 2023 17:08:40 +0800 Subject: [PATCH 013/117] fix: --connect command on macOS & window closing issues --- flutter/lib/common.dart | 22 ++++++++++-------- flutter/lib/consts.dart | 3 ++- .../lib/desktop/widgets/tabbar_widget.dart | 12 ++++++---- flutter/lib/main.dart | 8 +++---- flutter/lib/utils/multi_window_manager.dart | 23 ++++++++++++++++++- flutter/lib/utils/platform_channel.dart | 6 +++++ flutter/macos/Runner/AppDelegate.swift | 9 ++++---- flutter/macos/Runner/Info.plist | 10 ++++---- flutter/macos/Runner/MainFlutterWindow.swift | 3 +++ 9 files changed, 68 insertions(+), 28 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index a2623ff15..c058ec434 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -3,14 +3,11 @@ import 'dart:convert'; import 'dart:ffi' hide Size; import 'dart:io'; import 'dart:math'; -import 'dart:typed_data'; import 'package:back_button_interceptor/back_button_interceptor.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_hbb/utils/platform_channel.dart'; -import 'package:win32/win32.dart' as win32; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -19,14 +16,17 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; +import 'package:flutter_hbb/utils/platform_channel.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:uni_links/uni_links.dart'; import 'package:uni_links_desktop/uni_links_desktop.dart'; -import 'package:window_manager/window_manager.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:window_size/window_size.dart' as window_size; import 'package:url_launcher/url_launcher.dart'; +import 'package:win32/win32.dart' as win32; +import 'package:window_manager/window_manager.dart'; +import 'package:window_size/window_size.dart' as window_size; +import '../consts.dart'; import 'common/widgets/overlay.dart'; import 'mobile/pages/file_manager_page.dart'; import 'mobile/pages/remote_page.dart'; @@ -34,8 +34,6 @@ import 'models/input_model.dart'; import 'models/model.dart'; import 'models/platform_model.dart'; -import '../consts.dart'; - final globalKey = GlobalKey(); final navigationBarKey = GlobalKey(); @@ -1275,9 +1273,11 @@ Future restoreWindowPosition(WindowType type, {int? windowId}) async { /// initUniLinks should only be used on macos/windows. /// we use dbus for linux currently. Future initUniLinks() async { - if (!Platform.isWindows && !Platform.isMacOS) { + if (Platform.isLinux) { return; } + // Register uni links for Windows. The required info of url scheme is already + // declared in `Info.plist` for macOS. if (Platform.isWindows) { registerProtocol('rustdesk'); } @@ -1508,8 +1508,12 @@ Future onActiveWindowChanged() async { } catch (err) { debugPrintStack(label: "$err"); } finally { + debugPrint("Start closing RustDesk..."); await windowManager.setPreventClose(false); await windowManager.close(); + if (Platform.isMacOS) { + RdPlatformChannel.instance.terminate(); + } } } } diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index e4081d9a5..f48b612a8 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -1,9 +1,10 @@ -import 'package:flutter/material.dart'; import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; const double kDesktopRemoteTabBarHeight = 28.0; +const int kMainWindowId = 0; const String kPeerPlatformWindows = "Windows"; const String kPeerPlatformLinux = "Linux"; diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 598b2cc4c..223076951 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -1,23 +1,23 @@ -import 'dart:io'; import 'dart:async'; +import 'dart:io'; import 'dart:math'; import 'dart:ui' as ui; +import 'package:bot_toast/bot_toast.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart' hide TabBarTheme; import 'package:flutter_hbb/common.dart'; +import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/main.dart'; -import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart'; import 'package:scroll_pos/scroll_pos.dart'; import 'package:window_manager/window_manager.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:bot_toast/bot_toast.dart'; import '../../utils/multi_window_manager.dart'; @@ -527,7 +527,9 @@ class WindowActionPanelState extends State void onWindowClose() async { // hide window on close if (widget.isMainWindow) { - await rustDeskWinManager.unregisterActiveWindow(0); + if (rustDeskWinManager.getActiveWindows().contains(kMainWindowId)) { + await rustDeskWinManager.unregisterActiveWindow(kMainWindowId); + } // `hide` must be placed after unregisterActiveWindow, because once all windows are hidden, // flutter closes the application on macOS. We should ensure the post-run logic has ran successfully. // e.g.: saving window position. diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 5b1e0c37c..b41cc17df 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -1,22 +1,22 @@ import 'dart:convert'; import 'dart:io'; +import 'package:bot_toast/bot_toast.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; -import 'package:flutter_hbb/desktop/pages/server_page.dart'; import 'package:flutter_hbb/desktop/pages/install_page.dart'; +import 'package:flutter_hbb/desktop/pages/server_page.dart'; import 'package:flutter_hbb/desktop/screen/desktop_file_transfer_screen.dart'; import 'package:flutter_hbb/desktop/screen/desktop_port_forward_screen.dart'; import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart'; import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; -import 'package:bot_toast/bot_toast.dart'; // import 'package:window_manager/window_manager.dart'; @@ -139,8 +139,8 @@ void runMainApp(bool startService) async { rustDeskWinManager.registerActiveWindow(kWindowMainId); } windowManager.setOpacity(1); + windowManager.setTitle(getWindowName()); }); - windowManager.setTitle(getWindowName()); } void runMobileApp() async { diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index 550e9ab08..3af189ef6 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -160,6 +160,24 @@ class RustDeskMultiWindowManager { return null; } + void clearWindowType(WindowType type) { + switch (type) { + case WindowType.Main: + return; + case WindowType.RemoteDesktop: + _remoteDesktopWindowId = null; + break; + case WindowType.FileTransfer: + _fileTransferWindowId = null; + break; + case WindowType.PortForward: + _portForwardWindowId = null; + break; + case WindowType.Unknown: + break; + } + } + void setMethodHandler( Future Function(MethodCall call, int fromWindowId)? handler) { DesktopMultiWindow.setMethodHandler(handler); @@ -186,8 +204,11 @@ class RustDeskMultiWindowManager { } await WindowController.fromWindowId(wId).setPreventClose(false); await WindowController.fromWindowId(wId).close(); - } on Error { + } catch (e) { + debugPrint("$e"); return; + } finally { + clearWindowType(type); } } } diff --git a/flutter/lib/utils/platform_channel.dart b/flutter/lib/utils/platform_channel.dart index 1a36fb7a5..7b60ef63c 100644 --- a/flutter/lib/utils/platform_channel.dart +++ b/flutter/lib/utils/platform_channel.dart @@ -31,4 +31,10 @@ class RdPlatformChannel { return _osxMethodChannel .invokeMethod("setWindowTheme", {"themeName": theme.name}); } + + /// Terminate .app manually. + Future terminate() { + assert(Platform.isMacOS); + return _osxMethodChannel.invokeMethod("terminate"); + } } diff --git a/flutter/macos/Runner/AppDelegate.swift b/flutter/macos/Runner/AppDelegate.swift index 5708e35cb..3498decd3 100644 --- a/flutter/macos/Runner/AppDelegate.swift +++ b/flutter/macos/Runner/AppDelegate.swift @@ -3,21 +3,22 @@ import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { - var lauched = false; + var launched = false; override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { dummy_method_to_enforce_bundling() - return true + // https://github.com/leanflutter/window_manager/issues/214 + return false } override func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool { - if (lauched) { + if (launched) { handle_applicationShouldOpenUntitledFile(); } return true } override func applicationDidFinishLaunching(_ aNotification: Notification) { - lauched = true; + launched = true; NSApplication.shared.activate(ignoringOtherApps: true); } } diff --git a/flutter/macos/Runner/Info.plist b/flutter/macos/Runner/Info.plist index d1077e0e4..c926019ab 100644 --- a/flutter/macos/Runner/Info.plist +++ b/flutter/macos/Runner/Info.plist @@ -23,8 +23,10 @@ CFBundleTypeRole Editor - CFBundleURLName + CFBundleURLIconFile + CFBundleURLName + com.carriez.rustdesk CFBundleURLSchemes rustdesk @@ -35,13 +37,13 @@ $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) + LSUIElement + 1 NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass - NSApplication - LSUIElement - 1 + NSApplication diff --git a/flutter/macos/Runner/MainFlutterWindow.swift b/flutter/macos/Runner/MainFlutterWindow.swift index cea1e94bb..042840569 100644 --- a/flutter/macos/Runner/MainFlutterWindow.swift +++ b/flutter/macos/Runner/MainFlutterWindow.swift @@ -78,6 +78,9 @@ class MainFlutterWindow: NSWindow { self.setWindowInterfaceMode(window: window,themeName: themeName ?? "light") result(nil) break; + case "terminate": + NSApplication.shared.terminate(self) + result(nil) default: result(FlutterMethodNotImplemented) } From c13c89c0d6f09a14daea21b4a2e4cf5dd4bd4dff Mon Sep 17 00:00:00 2001 From: Kingtous Date: Fri, 3 Feb 2023 18:52:22 +0800 Subject: [PATCH 014/117] fix: uni links cause main window show --- flutter/lib/common.dart | 17 ++++++++++------- flutter/lib/main.dart | 7 +++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index c058ec434..7e22e0848 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1272,9 +1272,9 @@ Future restoreWindowPosition(WindowType type, {int? windowId}) async { /// [Availability] /// initUniLinks should only be used on macos/windows. /// we use dbus for linux currently. -Future initUniLinks() async { +Future initUniLinks() async { if (Platform.isLinux) { - return; + return false; } // Register uni links for Windows. The required info of url scheme is already // declared in `Info.plist` for macOS. @@ -1285,11 +1285,12 @@ Future initUniLinks() async { try { final initialLink = await getInitialLink(); if (initialLink == null) { - return; + return false; } - parseRustdeskUri(initialLink); + return parseRustdeskUri(initialLink); } catch (err) { debugPrintStack(label: "$err"); + return false; } } @@ -1310,11 +1311,13 @@ StreamSubscription? listenUniLinks() { return sub; } -/// Returns true if we successfully handle the startup arguments. +/// Handle command line arguments +/// +/// * Returns true if we successfully handle the startup arguments. bool checkArguments() { // bootArgs:[--connect, 362587269, --switch_uuid, e3d531cc-5dce-41e0-bd06-5d4a2b1eec05] // check connect args - final connectIndex = kBootArgs.indexOf("--connect"); + var connectIndex = kBootArgs.indexOf("--connect"); if (connectIndex == -1) { return false; } @@ -1368,7 +1371,7 @@ bool callUniLinksUriHandler(Uri uri) { Future.delayed(Duration.zero, () { rustDeskWinManager.newRemoteDesktop(peerId, switch_uuid: switch_uuid); }); - return false; + return true; } return false; } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index b41cc17df..67a243eff 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -114,7 +114,6 @@ Future initEnv(String appType) async { void runMainApp(bool startService) async { // register uni links - initUniLinks(); await initEnv(kAppTypeMain); // trigger connection status updater await bind.mainCheckConnectStatus(); @@ -130,7 +129,11 @@ void runMainApp(bool startService) async { // Restore the location of the main window before window hide or show. await restoreWindowPosition(WindowType.Main); // Check the startup argument, if we successfully handle the argument, we keep the main window hidden. - if (checkArguments()) { + final handledByUniLinks = await initUniLinks(); + final handledByCli = checkArguments(); + debugPrint( + "handled by uni links: $handledByUniLinks, handled by cli: $handledByCli"); + if (handledByUniLinks || handledByCli) { windowManager.hide(); } else { windowManager.show(); From ca97826b80e09979f29f2c96993e6125d42e0e36 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 3 Feb 2023 19:17:59 +0800 Subject: [PATCH 015/117] update cursor position when menu is dismissed Signed-off-by: fufesou --- flutter/lib/desktop/widgets/popup_menu.dart | 55 ++++++++++++++++--- .../lib/desktop/widgets/remote_menubar.dart | 45 ++++++++++++++- 2 files changed, 90 insertions(+), 10 deletions(-) diff --git a/flutter/lib/desktop/widgets/popup_menu.dart b/flutter/lib/desktop/widgets/popup_menu.dart index 0cbdad929..9833dcbca 100644 --- a/flutter/lib/desktop/widgets/popup_menu.dart +++ b/flutter/lib/desktop/widgets/popup_menu.dart @@ -109,13 +109,17 @@ class MenuConfig { this.boxWidth}); } +typedef DismissCallback = Function(); + abstract class MenuEntryBase { bool dismissOnClicked; + DismissCallback? dismissCallback; RxBool? enabled; MenuEntryBase({ this.dismissOnClicked = false, this.enabled, + this.dismissCallback, }); List> build(BuildContext context, MenuConfig conf); @@ -146,12 +150,14 @@ class MenuEntryRadioOption { String value; bool dismissOnClicked; RxBool? enabled; + DismissCallback? dismissCallback; MenuEntryRadioOption({ required this.text, required this.value, this.dismissOnClicked = false, this.enabled, + this.dismissCallback, }); } @@ -177,8 +183,13 @@ class MenuEntryRadios extends MenuEntryBase { required this.optionSetter, this.padding, dismissOnClicked = false, + dismissCallback, RxBool? enabled, - }) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) { + }) : super( + dismissOnClicked: dismissOnClicked, + enabled: enabled, + dismissCallback: dismissCallback, + ) { () async { _curOption.value = await curOptionGetter(); }(); @@ -249,6 +260,9 @@ class MenuEntryRadios extends MenuEntryBase { onPressed() { if (opt.dismissOnClicked && Navigator.canPop(context)) { Navigator.pop(context); + if (opt.dismissCallback != null) { + opt.dismissCallback!(); + } } setOption(opt.value); } @@ -360,6 +374,9 @@ class MenuEntrySubRadios extends MenuEntryBase { onPressed: () { if (opt.dismissOnClicked && Navigator.canPop(context)) { Navigator.pop(context); + if (opt.dismissCallback != null) { + opt.dismissCallback!(); + } } setOption(opt.value); }, @@ -421,7 +438,12 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { this.textStyle, this.padding, RxBool? enabled, - }) : super(dismissOnClicked: dismissOnClicked, enabled: enabled); + dismissCallback, + }) : super( + dismissOnClicked: dismissOnClicked, + enabled: enabled, + dismissCallback: dismissCallback, + ); RxBool get curOption; Future setOption(bool? option); @@ -463,6 +485,9 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { if (super.dismissOnClicked && Navigator.canPop(context)) { Navigator.pop(context); + if (super.dismissCallback != null) { + super.dismissCallback!(); + } } setOption(v); }, @@ -474,6 +499,9 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { if (super.dismissOnClicked && Navigator.canPop(context)) { Navigator.pop(context); + if (super.dismissCallback != null) { + super.dismissCallback!(); + } } setOption(v); }, @@ -485,6 +513,9 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { onPressed: () { if (super.dismissOnClicked && Navigator.canPop(context)) { Navigator.pop(context); + if (super.dismissCallback != null) { + super.dismissCallback!(); + } } setOption(!curOption.value); }, @@ -508,6 +539,7 @@ class MenuEntrySwitch extends MenuEntrySwitchBase { EdgeInsets? padding, dismissOnClicked = false, RxBool? enabled, + dismissCallback, }) : super( switchType: switchType, text: text, @@ -515,6 +547,7 @@ class MenuEntrySwitch extends MenuEntrySwitchBase { padding: padding, dismissOnClicked: dismissOnClicked, enabled: enabled, + dismissCallback: dismissCallback, ) { () async { _curOption.value = await getter(); @@ -551,12 +584,15 @@ class MenuEntrySwitch2 extends MenuEntrySwitchBase { EdgeInsets? padding, dismissOnClicked = false, RxBool? enabled, + dismissCallback, }) : super( - switchType: switchType, - text: text, - textStyle: textStyle, - padding: padding, - dismissOnClicked: dismissOnClicked); + switchType: switchType, + text: text, + textStyle: textStyle, + padding: padding, + dismissOnClicked: dismissOnClicked, + dismissCallback: dismissCallback, + ); @override RxBool get curOption => getter(); @@ -627,9 +663,11 @@ class MenuEntryButton extends MenuEntryBase { this.padding, dismissOnClicked = false, RxBool? enabled, + dismissCallback, }) : super( dismissOnClicked: dismissOnClicked, enabled: enabled, + dismissCallback: dismissCallback, ); Widget _buildChild(BuildContext context, MenuConfig conf) { @@ -641,6 +679,9 @@ class MenuEntryButton extends MenuEntryBase { ? () { if (super.dismissOnClicked && Navigator.canPop(context)) { Navigator.pop(context); + if (super.dismissCallback != null) { + super.dismissCallback!(); + } } proc(); } diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 2a84dcf14..5b418bcc4 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -231,6 +231,8 @@ class _RemoteMenubarState extends State { ); } + _menuDismissCallback() => widget.ffi.inputModel.refreshMousePos(); + Widget _buildMenubar(BuildContext context) { final List menubarItems = []; if (!isWebDesktop) { @@ -374,6 +376,7 @@ class _RemoteMenubarState extends State { onPressed: () { if (Navigator.canPop(context)) { Navigator.pop(context); + _menuDismissCallback(); } RxInt display = CurrentDisplayState.find(widget.id); if (display.value != i) { @@ -551,6 +554,7 @@ class _RemoteMenubarState extends State { onPressed: () { if (Navigator.canPop(context)) { Navigator.pop(context); + _menuDismissCallback(); } showSetOSPassword( widget.id, false, widget.ffi.dialogManager); @@ -563,6 +567,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), MenuEntryButton( childBuilder: (TextStyle? style) => Text( @@ -574,6 +579,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), MenuEntryButton( childBuilder: (TextStyle? style) => Text( @@ -585,6 +591,7 @@ class _RemoteMenubarState extends State { connect(context, widget.id, isTcpTunneling: true); }, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), ]); // {handler.get_audit_server() &&
  • {translate('Note')}
  • } @@ -602,6 +609,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), ); } @@ -618,6 +626,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); } } @@ -635,6 +644,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); } @@ -649,6 +659,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); if (pi.platform == kPeerPlatformWindows) { @@ -667,6 +678,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); } if (pi.platform != kPeerPlatformAndroid && @@ -681,6 +693,7 @@ class _RemoteMenubarState extends State { showConfirmSwitchSidesDialog(widget.id, widget.ffi.dialogManager), padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); } } @@ -696,6 +709,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); } @@ -717,6 +731,7 @@ class _RemoteMenubarState extends State { // }, // padding: padding, // dismissOnClicked: true, + // dismissCallback: _menuDismissCallback, // )); // } } @@ -762,11 +777,13 @@ class _RemoteMenubarState extends State { text: translate('Scale original'), value: kRemoteViewStyleOriginal, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), MenuEntryRadioOption( text: translate('Scale adaptive'), value: kRemoteViewStyleAdaptive, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), ], curOptionGetter: () async { @@ -782,6 +799,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), MenuEntryDivider(), MenuEntryRadios( @@ -791,21 +809,26 @@ class _RemoteMenubarState extends State { text: translate('Good image quality'), value: kRemoteImageQualityBest, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), MenuEntryRadioOption( text: translate('Balanced'), value: kRemoteImageQualityBalanced, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), MenuEntryRadioOption( text: translate('Optimize reaction time'), value: kRemoteImageQualityLow, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), MenuEntryRadioOption( - text: translate('Custom'), - value: kRemoteImageQualityCustom, - dismissOnClicked: true), + text: translate('Custom'), + value: kRemoteImageQualityCustom, + dismissOnClicked: true, + dismissCallback: _menuDismissCallback, + ), ], curOptionGetter: () async => // null means peer id is not found, which there's no need to care about @@ -970,12 +993,14 @@ class _RemoteMenubarState extends State { text: translate('ScrollAuto'), value: kRemoteScrollStyleAuto, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, enabled: widget.ffi.canvasModel.imageOverflow, ), MenuEntryRadioOption( text: translate('Scrollbar'), value: kRemoteScrollStyleBar, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, enabled: widget.ffi.canvasModel.imageOverflow, ), ], @@ -988,6 +1013,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); displayMenu.insert(3, MenuEntryDivider()); @@ -1058,6 +1084,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), ); } @@ -1084,11 +1111,13 @@ class _RemoteMenubarState extends State { text: translate('Auto'), value: 'auto', dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), MenuEntryRadioOption( text: 'VP9', value: 'vp9', dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ), ]; if (codecs[0]) { @@ -1096,6 +1125,7 @@ class _RemoteMenubarState extends State { text: 'H264', value: 'h264', dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); } if (codecs[1]) { @@ -1103,6 +1133,7 @@ class _RemoteMenubarState extends State { text: 'H265', value: 'h265', dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); } return list; @@ -1119,6 +1150,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); } } @@ -1141,6 +1173,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ); }()); } @@ -1163,6 +1196,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, ); }()); } @@ -1182,6 +1216,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); final perms = widget.ffi.ffiModel.permissions; @@ -1219,6 +1254,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: true, + dismissCallback: _menuDismissCallback, )); } } @@ -1290,6 +1326,7 @@ class _RemoteMenubarState extends State { onPressed: () { if (Navigator.canPop(context)) { Navigator.pop(context); + _menuDismissCallback(); } showKBLayoutTypeChooser( localPlatform, widget.ffi.dialogManager); @@ -1302,6 +1339,7 @@ class _RemoteMenubarState extends State { proc: () {}, padding: EdgeInsets.zero, dismissOnClicked: false, + dismissCallback: _menuDismissCallback, ), ); } @@ -1321,6 +1359,7 @@ class _RemoteMenubarState extends State { }, padding: padding, dismissOnClicked: dismissOnClicked, + dismissCallback: _menuDismissCallback, ); } } From 0940c93a481b90652af890e320ace5c4e00dde3e Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 3 Feb 2023 20:27:05 +0800 Subject: [PATCH 016/117] show cursor on conn is established Signed-off-by: fufesou --- flutter/lib/models/model.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 8a7a1005d..d032719e9 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -904,10 +904,10 @@ class CursorModel with ChangeNotifier { double _hoty = 0; double _displayOriginX = 0; double _displayOriginY = 0; - bool _firstUpdateMousePos = false; + DateTime? _firstUpdateMouseTime; bool gotMouseControl = true; DateTime _lastPeerMouse = DateTime.now() - .subtract(Duration(milliseconds: 2 * kMouseControlTimeoutMSec)); + .subtract(Duration(milliseconds: 3000 * kMouseControlTimeoutMSec)); String id = ''; WeakReference parent; @@ -926,6 +926,15 @@ class CursorModel with ChangeNotifier { DateTime.now().difference(_lastPeerMouse).inMilliseconds < kMouseControlTimeoutMSec; + bool isConnIn2Secs() { + if (_firstUpdateMouseTime == null) { + _firstUpdateMouseTime = DateTime.now(); + return true; + } else { + return DateTime.now().difference(_firstUpdateMouseTime!).inSeconds < 2; + } + } + CursorModel(this.parent); Set get cachedKeys => _cacheKeys; @@ -1122,12 +1131,10 @@ class CursorModel with ChangeNotifier { /// Update the cursor position. updateCursorPosition(Map evt, String id) async { - if (!_firstUpdateMousePos) { - _firstUpdateMousePos = true; - } else { + if (!isConnIn2Secs()) { gotMouseControl = false; + _lastPeerMouse = DateTime.now(); } - _lastPeerMouse = DateTime.now(); _x = double.parse(evt['x']); _y = double.parse(evt['y']); try { From 0d36166ea88847510426433dee115e17c693fe25 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 3 Feb 2023 23:07:47 +0800 Subject: [PATCH 017/117] sync option after toggle Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_tab_page.dart | 6 +++--- flutter/lib/desktop/widgets/remote_menubar.dart | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 55124fbcc..d832db0c6 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -273,6 +273,7 @@ class _ConnectionTabPageState extends State { menu.add(MenuEntryDivider()); menu.add(() { final state = ShowRemoteCursorState.find(key); + final optKey = 'show-remote-cursor'; return MenuEntrySwitch2( switchType: SwitchType.scheckbox, text: translate('Show remote cursor'), @@ -280,9 +281,8 @@ class _ConnectionTabPageState extends State { return state; }, setter: (bool v) async { - state.value = v; - await bind.sessionToggleOption( - id: key, value: 'show-remote-cursor'); + await bind.sessionToggleOption(id: key, value: optKey); + state.value = bind.sessionGetToggleOptionSync(id: key, arg: optKey); cancelFunc(); }, padding: padding, diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 5b418bcc4..d6b1cec72 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1160,6 +1160,7 @@ class _RemoteMenubarState extends State { if (!widget.ffi.canvasModel.cursorEmbedded) { displayMenu.add(() { final state = ShowRemoteCursorState.find(widget.id); + final optKey = 'show-remote-cursor'; return MenuEntrySwitch2( switchType: SwitchType.scheckbox, text: translate('Show remote cursor'), @@ -1167,9 +1168,9 @@ class _RemoteMenubarState extends State { return state; }, setter: (bool v) async { - state.value = v; - await bind.sessionToggleOption( - id: widget.id, value: 'show-remote-cursor'); + await bind.sessionToggleOption(id: widget.id, value: optKey); + state.value = + bind.sessionGetToggleOptionSync(id: widget.id, arg: optKey); }, padding: padding, dismissOnClicked: true, From 96a7182ff85ce35f47d46a4c5ff8c9a3258bad15 Mon Sep 17 00:00:00 2001 From: solokot Date: Fri, 3 Feb 2023 20:05:48 +0300 Subject: [PATCH 018/117] update ru.rs --- src/lang/ru.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 7ec6c1554..54b064c18 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -436,14 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Switch Sides", "Переключить стороны"), ("Please confirm if you want to share your desktop?", "Подтверждаете, что хотите поделиться своим рабочим столом?"), ("Closed as expected", "Закрыто по ожиданию"), - ("Display", ""), - ("Default View Style", ""), - ("Default Scroll Style", ""), - ("Default Image Quality", ""), - ("Default Codec", ""), - ("Bitrate", ""), - ("FPS", ""), - ("Auto", ""), - ("Other Default Options", ""), + ("Display", "Отображение"), + ("Default View Style", "Стиль отображения по умолчанию"), + ("Default Scroll Style", "Стиль прокрутки по умолчанию"), + ("Default Image Quality", "Качество изображения по умолчанию"), + ("Default Codec", "Кодек по умолчанию"), + ("Bitrate", "Битрейт"), + ("FPS", "FPS"), + ("Auto", "Авто"), + ("Other Default Options", "Другие параметры по умолчанию"), ].iter().cloned().collect(); } From f9d106ea745017b1c7c2e8c69b2ea1ba6dc670c4 Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Fri, 3 Feb 2023 22:36:50 +0100 Subject: [PATCH 019/117] Update de.rs --- src/lang/de.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index 5b68c0e7a..2d6d3d069 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -200,7 +200,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Warnung"), ("Login screen using Wayland is not supported", "Anmeldebildschirm mit Wayland wird nicht unterstützt."), ("Reboot required", "Neustart erforderlich"), - ("Unsupported display server ", "Nicht unterstützter Display-Server"), + ("Unsupported display server ", "Nicht unterstützter Anzeigeserver"), ("x11 expected", "X11 erwartet"), ("Port", "Port"), ("Settings", "Einstellungen"), @@ -327,7 +327,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Mobile Actions", "Mobile Aktionen"), ("Select Monitor", "Bildschirm auswählen"), ("Control Actions", "Aktionen"), - ("Display Settings", "Bildschirmeinstellungen"), + ("Display Settings", "Anzeigeeinstellungen"), ("Ratio", "Verhältnis"), ("Image Quality", "Bildqualität"), ("Scroll Style", "Scroll-Stil"), @@ -338,7 +338,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Secure Connection", "Sichere Verbindung"), ("Insecure Connection", "Unsichere Verbindung"), ("Scale original", "Keine Skalierung"), - ("Scale adaptive", "Automatische Skalierung"), + ("Scale adaptive", "Anpassbare Skalierung"), ("General", "Allgemein"), ("Security", "Sicherheit"), ("Theme", "Farbgebung"), @@ -358,7 +358,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clear", "Zurücksetzen"), ("Audio Input Device", "Audioeingabegerät"), ("Deny remote access", "Fernzugriff verbieten"), - ("Use IP Whitelisting", "IP-Whitelist benutzen"), + ("Use IP Whitelisting", "IP-Whitelist verwenden"), ("Network", "Netzwerk"), ("Enable RDP", "RDP aktivieren"), ("Pin menubar", "Menüleiste anpinnen"), @@ -436,14 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Switch Sides", "Seiten wechseln"), ("Please confirm if you want to share your desktop?", "Bitte bestätigen Sie, ob Sie Ihren Desktop freigeben möchten."), ("Closed as expected", "Wie erwartet geschlossen"), - ("Display", ""), - ("Default View Style", ""), - ("Default Scroll Style", ""), - ("Default Image Quality", ""), - ("Default Codec", ""), - ("Bitrate", ""), - ("FPS", ""), - ("Auto", ""), - ("Other Default Options", ""), + ("Display", "Anzeige"), + ("Default View Style", "Standard-Ansichtsstil"), + ("Default Scroll Style", "Standard-Scroll-Stil"), + ("Default Image Quality", "Standard-Bildqualität"), + ("Default Codec", "Standard-Codec"), + ("Bitrate", "Bitrate"), + ("FPS", "fps"), + ("Auto", "Automatisch"), + ("Other Default Options", "Weitere Standardoptionen"), ].iter().cloned().collect(); } From 3a1b9781124e0a59f64c5ea4cbc30ebdf1fe742d Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sat, 4 Feb 2023 01:10:32 +0800 Subject: [PATCH 020/117] feat: add event handler on rust macos --- Cargo.lock | 41 +++++++++++++++++-- Cargo.toml | 2 + .../macos/Runner.xcodeproj/project.pbxproj | 5 ++- flutter/macos/Runner/MainFlutterWindow.swift | 1 - src/ui/macos.rs | 27 ++++++++++-- 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c4af56e9..e15641363 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1137,7 +1137,7 @@ checksum = "413487ef345ab5cdfbf23e66070741217a701bce70f2f397a54221b4f2b6056a" dependencies = [ "dconf_rs", "detect-desktop-environment", - "dirs", + "dirs 4.0.0", "objc", "rust-ini", "web-sys", @@ -1401,6 +1401,16 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + [[package]] name = "dirs" version = "4.0.0" @@ -1873,6 +1883,19 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fruitbasket" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898289b8e0528c84fb9b88f15ac9d5109bcaf23e0e49bb6f64deee0d86b6a351" +dependencies = [ + "dirs 2.0.2", + "objc", + "objc-foundation", + "objc_id", + "time 0.1.45", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -3657,6 +3680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", + "objc_exception", ] [[package]] @@ -3670,6 +3694,15 @@ dependencies = [ "objc_id", ] +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "objc_id" version = "0.1.1" @@ -4655,6 +4688,7 @@ dependencies = [ "flexi_logger", "flutter_rust_bridge", "flutter_rust_bridge_codegen", + "fruitbasket", "glib 0.16.5", "gtk", "hbb_common", @@ -4673,6 +4707,7 @@ dependencies = [ "mouce", "num_cpus", "objc", + "objc_id", "parity-tokio-ipc", "rdev", "repng", @@ -4713,7 +4748,7 @@ name = "rustdesk-portable-packer" version = "0.1.0" dependencies = [ "brotli", - "dirs", + "dirs 4.0.0", "embed-resource", "md5", ] @@ -6591,7 +6626,7 @@ dependencies = [ "async-trait", "byteorder", "derivative", - "dirs", + "dirs 4.0.0", "enumflags2", "event-listener", "futures-core", diff --git a/Cargo.toml b/Cargo.toml index 1e9af30e5..936b9e349 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,6 +106,8 @@ core-graphics = "0.22" include_dir = "0.7.2" tray-item = "0.7" # looks better than trayicon dark-light = "0.2" +fruitbasket = "0.10.0" +objc_id = "0.1.1" [target.'cfg(target_os = "linux")'.dependencies] psimple = { package = "libpulse-simple-binding", version = "2.25" } diff --git a/flutter/macos/Runner.xcodeproj/project.pbxproj b/flutter/macos/Runner.xcodeproj/project.pbxproj index 7a17c3de1..066560203 100644 --- a/flutter/macos/Runner.xcodeproj/project.pbxproj +++ b/flutter/macos/Runner.xcodeproj/project.pbxproj @@ -227,7 +227,7 @@ TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1100; + LastSwiftMigration = 1420; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { @@ -463,6 +463,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Profile; @@ -607,6 +608,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; "SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -643,6 +645,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; "SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h; SWIFT_VERSION = 5.0; }; diff --git a/flutter/macos/Runner/MainFlutterWindow.swift b/flutter/macos/Runner/MainFlutterWindow.swift index 042840569..97b46bb84 100644 --- a/flutter/macos/Runner/MainFlutterWindow.swift +++ b/flutter/macos/Runner/MainFlutterWindow.swift @@ -87,4 +87,3 @@ class MainFlutterWindow: NSWindow { }) } } - diff --git a/src/ui/macos.rs b/src/ui/macos.rs index 7daef8eab..39812cf90 100644 --- a/src/ui/macos.rs +++ b/src/ui/macos.rs @@ -1,3 +1,5 @@ +use std::{ffi::c_void, rc::Rc}; + #[cfg(target_os = "macos")] use cocoa::{ appkit::{NSApp, NSApplication, NSApplicationActivationPolicy::*, NSMenu, NSMenuItem}, @@ -8,11 +10,14 @@ use objc::{ class, declare::ClassDecl, msg_send, - runtime::{Object, Sel, BOOL}, + runtime::{BOOL, Object, Sel}, sel, sel_impl, }; -use sciter::{make_args, Host}; -use std::{ffi::c_void, rc::Rc}; +use objc::runtime::Class; +use objc_id::WeakId; +use sciter::{Host, make_args}; + +use hbb_common::log; static APP_HANDLER_IVAR: &str = "GoDeskAppHandler"; @@ -98,12 +103,21 @@ unsafe fn set_delegate(handler: Option>) { sel!(handleMenuItem:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); + decl.add_method(sel!(handleEvent:withReplyEvent:), handle_apple_event as extern fn(&Object, Sel, u64, u64)); let decl = decl.register(); let delegate: id = msg_send![decl, alloc]; let () = msg_send![delegate, init]; let state = DelegateState { handler }; let handler_ptr = Box::into_raw(Box::new(state)); (*delegate).set_ivar(APP_HANDLER_IVAR, handler_ptr as *mut c_void); + // Set the url scheme handler + let cls = Class::get("NSAppleEventManager").unwrap(); + let manager: *mut Object = msg_send![cls, sharedAppleEventManager]; + let _: () = msg_send![manager, + setEventHandler: delegate + andSelector: sel!(handleEvent:withReplyEvent:) + forEventClass: fruitbasket::kInternetEventClass + andEventID: fruitbasket::kAEGetURL]; let () = msg_send![NSApp(), setDelegate: delegate]; } @@ -167,6 +181,13 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { } } +extern fn handle_apple_event(this: &Object, _cmd: Sel, event: u64, _reply: u64) { + let event = event as *mut Object; + let url = fruitbasket::parse_url_event(event); + log::debug!("event found {}", url); + let _ = crate::run_me(vec![url]); +} + unsafe fn make_menu_item(title: &str, key: &str, tag: u32) -> *mut Object { let title = NSString::alloc(nil).init_str(title); let action = sel!(handleMenuItem:); From 7e69cbde26a1a62f3e319fdfebd12637a2dd2956 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sat, 4 Feb 2023 01:22:40 +0800 Subject: [PATCH 021/117] opt: support binary + uri links startup --- flutter/lib/common.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 7e22e0848..9f3e2c740 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1315,6 +1315,12 @@ StreamSubscription? listenUniLinks() { /// /// * Returns true if we successfully handle the startup arguments. bool checkArguments() { + if (kBootArgs.isNotEmpty) { + final ret = parseRustdeskUri(kBootArgs.first); + if (ret) { + return true; + } + } // bootArgs:[--connect, 362587269, --switch_uuid, e3d531cc-5dce-41e0-bd06-5d4a2b1eec05] // check connect args var connectIndex = kBootArgs.indexOf("--connect"); @@ -1352,7 +1358,7 @@ bool checkArguments() { bool parseRustdeskUri(String uriPath) { final uri = Uri.tryParse(uriPath); if (uri == null) { - print("uri is not valid: $uriPath"); + debugPrint("uri is not valid: $uriPath"); return false; } return callUniLinksUriHandler(uri); From a9fc63c34f6f0bb18701e9597d1a5d8568c35ccc Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sat, 4 Feb 2023 01:31:56 +0800 Subject: [PATCH 022/117] opt: add default url scheme handler for macos --- src/flutter_ffi.rs | 31 ++++++++++++++++++------------- src/ui/macos.rs | 9 +++++++-- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index d40c66d19..d001dd388 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -3,28 +3,29 @@ use std::{ ffi::{CStr, CString}, os::raw::c_char, }; +use std::str::FromStr; use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer}; use serde_json::json; -use crate::common::is_keyboard_mode_supported; -use hbb_common::message_proto::KeyboardMode; -use hbb_common::ResultType; use hbb_common::{ - config::{self, LocalConfig, PeerConfig, ONLINE}, + config::{self, LocalConfig, ONLINE, PeerConfig}, fs, log, }; -use std::str::FromStr; +use hbb_common::message_proto::KeyboardMode; +use hbb_common::ResultType; -// use crate::hbbs_http::account::AuthResult; - -use crate::flutter::{self, SESSIONS}; -use crate::ui_interface::{self, *}; use crate::{ client::file_trait::FileManager, common::make_fd_to_json, flutter::{session_add, session_start_}, }; +use crate::common::is_keyboard_mode_supported; +use crate::flutter::{self, SESSIONS}; +use crate::ui_interface::{self, *}; + +// use crate::hbbs_http::account::AuthResult; + fn initialize(app_dir: &str) { *config::APP_DIR.write().unwrap() = app_dir.to_owned(); #[cfg(target_os = "android")] @@ -910,6 +911,11 @@ pub fn main_start_dbus_server() { } } +pub fn osx_handle_uni_links(url: String) { + #![cfg(target_os = "macos")] + crate::ui::macos::handle_url_scheme(url); +} + pub fn session_send_mouse(id: String, msg: String) { if let Ok(m) = serde_json::from_str::>(&msg) { let alt = m.get("alt").is_some(); @@ -1257,13 +1263,12 @@ pub fn main_hide_docker() -> SyncReturn { #[cfg(target_os = "android")] pub mod server_side { + use hbb_common::log; use jni::{ + JNIEnv, objects::{JClass, JString}, sys::jstring, - JNIEnv, - }; - - use hbb_common::log; + }; use crate::start_server; diff --git a/src/ui/macos.rs b/src/ui/macos.rs index 39812cf90..94e75959c 100644 --- a/src/ui/macos.rs +++ b/src/ui/macos.rs @@ -181,11 +181,16 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { } } -extern fn handle_apple_event(this: &Object, _cmd: Sel, event: u64, _reply: u64) { +/// The function to handle the url scheme sent by system. +pub fn handle_url_scheme(url: String) { + unimplemented!(); +} + +extern fn handle_apple_event(_this: &Object, _cmd: Sel, event: u64, _reply: u64) { let event = event as *mut Object; let url = fruitbasket::parse_url_event(event); log::debug!("event found {}", url); - let _ = crate::run_me(vec![url]); + handle_url_scheme(url); } unsafe fn make_menu_item(title: &str, key: &str, tag: u32) -> *mut Object { From 4dfae8da1075450346ae72927faac8fc659027d5 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sat, 4 Feb 2023 11:23:36 +0800 Subject: [PATCH 023/117] feat: add url scheme handler for macos --- flutter/lib/consts.dart | 3 +- flutter/lib/main.dart | 2 +- flutter/lib/models/model.dart | 3 ++ flutter/lib/models/native_model.dart | 10 +++- src/flutter_ffi.rs | 25 +++++++--- src/ipc.rs | 11 +++- src/server.rs | 75 ++++++++++++++++++++++------ src/ui/macos.rs | 18 +++++-- 8 files changed, 116 insertions(+), 31 deletions(-) diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index f48b612a8..1fc97f410 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -11,8 +11,9 @@ const String kPeerPlatformLinux = "Linux"; const String kPeerPlatformMacOS = "Mac OS"; const String kPeerPlatformAndroid = "Android"; -/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)' , 'Desktop CM Page', "Install Page" +/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)', "Install Page" const String kAppTypeMain = "main"; +const String kAppTypeConnectionManager = "cm"; const String kAppTypeDesktopRemote = "remote"; const String kAppTypeDesktopFileTransfer = "file transfer"; const String kAppTypeDesktopPortForward = "port forward"; diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 67a243eff..86cc9d89b 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -211,7 +211,7 @@ void runMultiWindow( } void runConnectionManagerScreen(bool hide) async { - await initEnv(kAppTypeMain); + await initEnv(kAppTypeConnectionManager); _runApp( '', const DesktopServerPage(), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index d032719e9..aae4c6a07 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -199,6 +199,9 @@ class FfiModel with ChangeNotifier { final peer_id = evt['peer_id'].toString(); await bind.sessionSwitchSides(id: peer_id); closeConnection(id: peer_id); + } else if (name == "on_url_scheme_received") { + final url = evt['url'].toString(); + parseRustdeskUri(url); } }; } diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index cf2de4219..d6885bfb0 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -8,6 +8,7 @@ import 'package:external_path/external_path.dart'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hbb/consts.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:win32/win32.dart' as win32; @@ -46,6 +47,8 @@ class PlatformFFI { static get localeName => Platform.localeName; + static get isMain => instance._appType == kAppTypeMain; + static Future getVersion() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); return packageInfo.version; @@ -112,8 +115,11 @@ class PlatformFFI { } _ffiBind = RustdeskImpl(dylib); if (Platform.isLinux) { - // start dbus service, no need to await - await _ffiBind.mainStartDbusServer(); + // Start a dbus service, no need to await + _ffiBind.mainStartDbusServer(); + } else if (Platform.isMacOS) { + // Start an ipc server for handling url schemes. + _ffiBind.mainStartIpcUrlServer(); } _startListenEvent(_ffiBind); // global event try { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index d001dd388..5dccd9050 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1,8 +1,4 @@ -use std::{ - collections::HashMap, - ffi::{CStr, CString}, - os::raw::c_char, -}; +use std::{collections::HashMap, ffi::{CStr, CString}, os::raw::c_char, thread}; use std::str::FromStr; use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer}; @@ -1261,6 +1257,23 @@ pub fn main_hide_docker() -> SyncReturn { SyncReturn(true) } +/// Start an ipc server for receiving the url scheme. +/// +/// * Should only be called in the main flutter window. +/// * macOS only +pub fn main_start_ipc_url_server() { + #[cfg(target_os = "macos")] + thread::spawn(move || crate::server::start_ipc_url_server()); +} + +/// Send a url scheme throught the ipc. +/// +/// * macOS only +pub fn send_url_scheme(url: String) { + #[cfg(target_os = "macos")] + thread::spawn(move || crate::ui::macos::handle_url_scheme(url)); +} + #[cfg(target_os = "android")] pub mod server_side { use hbb_common::log; @@ -1268,7 +1281,7 @@ pub mod server_side { JNIEnv, objects::{JClass, JString}, sys::jstring, - }; + }; use crate::start_server; diff --git a/src/ipc.rs b/src/ipc.rs index d4d803aec..d610fb84d 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -16,10 +16,10 @@ use hbb_common::{ config::{self, Config, Config2}, futures::StreamExt as _, futures_util::sink::SinkExt, - log, password_security as password, timeout, tokio, + log, password_security as password, ResultType, timeout, + tokio, tokio::io::{AsyncRead, AsyncWrite}, tokio_util::codec::Framed, - ResultType, }; use crate::rendezvous_mediator::RendezvousMediator; @@ -210,6 +210,7 @@ pub enum Data { DataPortableService(DataPortableService), SwitchSidesRequest(String), SwitchSidesBack, + UrlLink(String) } #[tokio::main(flavor = "current_thread")] @@ -832,3 +833,9 @@ pub async fn test_rendezvous_server() -> ResultType<()> { c.send(&Data::TestRendezvousServer).await?; Ok(()) } + +#[tokio::main(flavor = "current_thread")] +pub async fn send_url_scheme(url: String) -> ResultType<()> { + connect(1_000, "_url").await?.send(&Data::UrlLink(url)).await?; + Ok(()) +} diff --git a/src/server.rs b/src/server.rs index 381e3df90..de213ae5a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,8 +1,13 @@ -use crate::ipc::Data; +use std::{ + collections::HashMap, + net::SocketAddr, + sync::{Arc, Mutex, RwLock, Weak}, + time::Duration, +}; + use bytes::Bytes; + pub use connection::*; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use hbb_common::config::Config2; use hbb_common::{ allow_err, anyhow::{anyhow, Context}, @@ -12,19 +17,19 @@ use hbb_common::{ message_proto::*, protobuf::{Enum, Message as _}, rendezvous_proto::*, + ResultType, socket_client, - sodiumoxide::crypto::{box_, secretbox, sign}, - timeout, tokio, ResultType, Stream, + sodiumoxide::crypto::{box_, secretbox, sign}, Stream, timeout, tokio, }; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use service::ServiceTmpl; +use hbb_common::config::Config2; +use hbb_common::tcp::new_listener; use service::{GenericService, Service, Subscriber}; -use std::{ - collections::HashMap, - net::SocketAddr, - sync::{Arc, Mutex, RwLock, Weak}, - time::Duration, -}; +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use service::ServiceTmpl; + +use crate::ipc::{connect, Data}; +use crate::ui_interface::SENDER; pub mod audio_service; cfg_if::cfg_if! { @@ -55,8 +60,6 @@ mod service; mod video_qos; pub mod video_service; -use hbb_common::tcp::new_listener; - pub type Childs = Arc>>; type ConnMap = HashMap; @@ -425,6 +428,50 @@ pub async fn start_server(is_server: bool) { } } +#[cfg(target_os = "macos")] +#[tokio::main(flavor = "current_thread")] +pub async fn start_ipc_url_server() { + log::debug!("Start an ipc server for listening to url schemes"); + match crate::ipc::new_listener("_url").await { + Ok(mut incoming) => { + while let Some(Ok(conn)) = incoming.next().await { + let mut conn = crate::ipc::Connection::new(conn); + match conn.next_timeout(1000).await { + Ok(Some(data)) => { + match data { + Data::UrlLink(url) => { + #[cfg(feature = "flutter")] + { + if let Some(stream) = crate::flutter::GLOBAL_EVENT_STREAM.read().unwrap().get( + crate::flutter::APP_TYPE_MAIN + ) { + let mut m = HashMap::new(); + m.insert("name", "on_url_scheme_received"); + m.insert("url", url.as_str()); + stream.add(serde_json::to_string(&m).unwrap()); + } else { + log::warn!("No main window app found!"); + } + } + } + _ => { + log::warn!("An unexpected data was sent to the ipc url server.") + } + } + } + Err(err) => { + log::error!("{}", err); + } + _ => {} + } + } + } + Err(err) => { + log::error!("{}", err); + } + } +} + #[cfg(target_os = "macos")] async fn sync_and_watch_config_dir() { if crate::platform::is_root() { diff --git a/src/ui/macos.rs b/src/ui/macos.rs index 94e75959c..98e355dc1 100644 --- a/src/ui/macos.rs +++ b/src/ui/macos.rs @@ -17,7 +17,9 @@ use objc::runtime::Class; use objc_id::WeakId; use sciter::{Host, make_args}; -use hbb_common::log; +use hbb_common::{log, tokio}; + +use crate::ui_cm_interface::start_ipc; static APP_HANDLER_IVAR: &str = "GoDeskAppHandler"; @@ -181,16 +183,22 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { } } -/// The function to handle the url scheme sent by system. +/// The function to handle the url scheme sent by the system. +/// +/// 1. Try to send the url scheme from ipc. +/// 2. If failed to send the url scheme, we open a new main window to handle this url scheme. pub fn handle_url_scheme(url: String) { - unimplemented!(); + if let Err(err) = crate::ipc::send_url_scheme(url.clone()) { + log::debug!("Send the url to the existing flutter process failed, {}. Let's open a new program to handle this.", err); + let _ = crate::run_me(vec![url]); + } } extern fn handle_apple_event(_this: &Object, _cmd: Sel, event: u64, _reply: u64) { let event = event as *mut Object; let url = fruitbasket::parse_url_event(event); - log::debug!("event found {}", url); - handle_url_scheme(url); + log::debug!("an event was received: {}", url); + std::thread::spawn(move || handle_url_scheme(url)); } unsafe fn make_menu_item(title: &str, key: &str, tag: u32) -> *mut Object { From a349be6428cca3675d777821b71554e4e49b0952 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sat, 4 Feb 2023 11:33:08 +0800 Subject: [PATCH 024/117] opt: remove unnecessary ffi func --- src/flutter_ffi.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 5dccd9050..ca9314c43 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -907,11 +907,6 @@ pub fn main_start_dbus_server() { } } -pub fn osx_handle_uni_links(url: String) { - #![cfg(target_os = "macos")] - crate::ui::macos::handle_url_scheme(url); -} - pub fn session_send_mouse(id: String, msg: String) { if let Ok(m) = serde_json::from_str::>(&msg) { let alt = m.get("alt").is_some(); From 151b115fc900ad15fd2fc79319e166b48c9b6661 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sat, 4 Feb 2023 13:37:48 +0800 Subject: [PATCH 025/117] fix: android build --- src/server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server.rs b/src/server.rs index de213ae5a..109fc1e9a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -29,7 +29,6 @@ use service::{GenericService, Service, Subscriber}; use service::ServiceTmpl; use crate::ipc::{connect, Data}; -use crate::ui_interface::SENDER; pub mod audio_service; cfg_if::cfg_if! { From dd00ea5abd24be98addc5444294f52908cbc729f Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sat, 4 Feb 2023 16:18:54 +0800 Subject: [PATCH 026/117] opt: reuse current main window when using url scheme --- src/core_main.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/core_main.rs b/src/core_main.rs index 89a962f1d..99d0e888e 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -1,4 +1,6 @@ -use hbb_common::log; +use std::future::Future; + +use hbb_common::{log, ResultType}; /// shared by flutter and sciter main function /// @@ -346,5 +348,11 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option Date: Sun, 5 Feb 2023 07:59:29 +0330 Subject: [PATCH 027/117] Update fa.rs --- src/lang/fa.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 72cde49f9..dd1c75bac 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -436,14 +436,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Switch Sides", "طرفین را عوض کنید"), ("Please confirm if you want to share your desktop?", "لطفاً تأیید کنید که آیا می خواهید دسکتاپ خود را به اشتراک بگذارید؟"), ("Closed as expected", "طبق انتظار بسته شد"), - ("Display", ""), - ("Default View Style", ""), - ("Default Scroll Style", ""), - ("Default Image Quality", ""), - ("Default Codec", ""), - ("Bitrate", ""), - ("FPS", ""), - ("Auto", ""), - ("Other Default Options", ""), + ("Display", "نمایش دادن"), + ("Default View Style", "سبک نمایش پیش فرض"), + ("Default Scroll Style", "سبک پیش‌فرض اسکرول"), + ("Default Image Quality", "کیفیت تصویر پیش فرض"), + ("Default Codec", "کدک پیش فرض"), + ("Bitrate", "میزان بیت صفحه نمایش"), + ("FPS", "FPS"), + ("Auto", "خودکار"), + ("Other Default Options", "سایر گزینه های پیش فرض"), ].iter().cloned().collect(); } From afb76c63261ac5d2f1602ec3d7627a1168ee11c6 Mon Sep 17 00:00:00 2001 From: mehdi-song Date: Sun, 5 Feb 2023 10:20:05 +0330 Subject: [PATCH 028/117] Update README-FA.md --- docs/README-FA.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/README-FA.md b/docs/README-FA.md index 02b156dbb..496e81849 100644 --- a/docs/README-FA.md +++ b/docs/README-FA.md @@ -1,6 +1,6 @@

    RustDesk - Your remote desktop
    - تصاویر محیط نرم‌افزار • + تصاویر محیط نرم‌افزارساختارداکرساخت • @@ -9,12 +9,12 @@

    [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt]

    برای ترجمه این سند (README)، رابط کاربری RustDesk، و مستندات آن به زبان مادری شما به کمکتان نیازمندیم.

    -با ما گپ بزنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) +با ما گفتگو کنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) -راست‌دسک (RustDesk) نرم‌افزاری برای گارکردن با رایانه‌ی رومیزی از راه دور است و با زبان برنامه‌نویسی Rust نوشته شده است. نیاز به تنظیمات چندانی ندارد و شما را قادر می سازد تا بدون نگرانی از امنیت اطلاعات خود بر آن‌ها کنترل کامل داشته باشید. +راست‌دسک (RustDesk) نرم‌افزاری برای کارکردن با رایانه‌ی رومیزی از راه دور است و با زبان برنامه‌نویسی Rust نوشته شده است. نیاز به تنظیمات چندانی ندارد و شما را قادر می سازد تا بدون نگرانی از امنیت اطلاعات خود بر آن‌ها کنترل کامل داشته باشید. می‌توانید از سرور rendezvous/relay ما استفاده کنید، [سرور خودتان را راه‌اندازی کنید](https://rustdesk.com/server) یا [ سرورrendezvous/relay خود را بنویسید](https://github.com/rustdesk/rustdesk). @@ -130,7 +130,7 @@ cd rustdesk docker build -t "rustdesk-builder" . ``` -سپس، هر بار که نیاز به ساخت ترم‌افزار داشتید، دستور زیر را اجرا کنید: +سپس، هر بار که نیاز به ساخت نرم‌افزار داشتید، دستور زیر را اجرا کنید: ```sh docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder From 3462756a11a8b69bed40246cd4a6362b291f7bfd Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sun, 5 Feb 2023 16:56:13 +0800 Subject: [PATCH 029/117] optimize dialog margin, fix password eye icon color --- flutter/lib/common.dart | 8 +++----- flutter/lib/consts.dart | 1 - flutter/lib/mobile/widgets/dialog.dart | 5 +---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 9f3e2c740..8236597ff 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -694,7 +694,6 @@ void msgBox(String id, String type, String title, String text, String link, buttons.insert( 0, dialogButton('Cancel', onPressed: cancel, isOutline: true)); } - // TODO: test this button if (type.contains("hasclose")) { buttons.insert( 0, @@ -708,8 +707,7 @@ void msgBox(String id, String type, String title, String text, String link, dialogManager.show( (setState, close) => CustomAlertDialog( title: null, - content: SelectionArea( - child: msgboxContent(type, title, text).paddingOnly(bottom: 10)), + content: SelectionArea(child: msgboxContent(type, title, text)), actions: buttons, onSubmit: hasOk ? submit : null, onCancel: hasCancel == true ? cancel : null, @@ -774,7 +772,7 @@ Widget msgboxContent(String type, String title, String text) { ), ), ], - ); + ).marginOnly(bottom: 12); } void msgBoxCommon(OverlayDialogManager dialogManager, String title, @@ -1714,4 +1712,4 @@ Future updateSystemWindowTheme() async { : SystemWindowTheme.dark); } } -} \ No newline at end of file +} diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 1fc97f410..c95c62fcc 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -26,7 +26,6 @@ const String kWindowEventShow = "show"; const String kWindowConnect = "connect"; const String kUniLinksPrefix = "rustdesk://"; -const String kActionNewConnection = "connection/new/"; const String kTabLabelHomePage = "Home"; const String kTabLabelSettingPage = "Settings"; diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index bded6d069..2fbe40091 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:flutter_hbb/desktop/widgets/button.dart'; import 'package:get/get.dart'; import '../../common.dart'; @@ -371,8 +370,7 @@ void showWaitUacDialog( tag: '$id-wait-uac', (setState, close) => CustomAlertDialog( title: null, - content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip') - .marginOnly(bottom: 10), + content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip'), )); } @@ -647,7 +645,6 @@ class _PasswordWidgetState extends State { icon: Icon( // Based on passwordVisible state choose the icon _passwordVisible ? Icons.visibility : Icons.visibility_off, - color: Theme.of(context).primaryColorDark, ), onPressed: () { // Update the state i.e. toggle the state of passwordVisible variable From 255c58ef7b725ea64012073ae8d8cb48720d7b98 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sun, 5 Feb 2023 17:29:54 +0800 Subject: [PATCH 030/117] opt: close button color and corner on tab --- flutter/lib/desktop/widgets/tabbar_widget.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 223076951..cfbddbafb 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -959,7 +959,7 @@ class _CloseButton extends StatelessWidget { offstage: !visible, child: InkWell( hoverColor: MyTheme.tabbar(context).closeHoverColor, - customBorder: const RoundedRectangleBorder(), + customBorder: const CircleBorder(), onTap: () => onClose(), child: Icon( Icons.close, @@ -1082,7 +1082,7 @@ class TabbarTheme extends ThemeExtension { unSelectedIconColor: Color.fromARGB(255, 96, 96, 96), dividerColor: Color.fromARGB(255, 238, 238, 238), hoverColor: Color.fromARGB(51, 158, 158, 158), - closeHoverColor: Colors.black, + closeHoverColor: Color.fromARGB(255, 224, 224, 224), selectedTabBackgroundColor: Color.fromARGB(255, 240, 240, 240)); static const dark = TabbarTheme( From 133fba573bea02d9a29b64e879d522f13d331069 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sun, 5 Feb 2023 18:20:22 +0800 Subject: [PATCH 031/117] confirmed issue #2935 is false report, set_bitrate was called, and bandwidth has obvious change if you watch car game video --- src/server/video_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/video_service.rs b/src/server/video_service.rs index d041a433c..55920e320 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -498,7 +498,7 @@ fn run(sp: GenericService) -> ResultType<()> { video_qos.target_bitrate, video_qos.fps ); - encoder.set_bitrate(video_qos.target_bitrate).unwrap(); + allow_err!(encoder.set_bitrate(video_qos.target_bitrate)); spf = video_qos.spf(); } drop(video_qos); From bb0f481df31fc6121a2c5782c158d0d4a3d3f66c Mon Sep 17 00:00:00 2001 From: botanicvelious Date: Sun, 5 Feb 2023 17:00:22 -0700 Subject: [PATCH 032/117] update rust build action to use the same on all --- .github/workflows/flutter-nightly.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/flutter-nightly.yml b/.github/workflows/flutter-nightly.yml index 83ad7629e..5ca284cee 100644 --- a/.github/workflows/flutter-nightly.yml +++ b/.github/workflows/flutter-nightly.yml @@ -59,10 +59,9 @@ jobs: - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: - toolchain: "1.62" + toolchain: stable target: ${{ matrix.job.target }} override: true - components: rustfmt profile: minimal # minimal component installation (ie, no documentation) - uses: Swatinem/rust-cache@v2 From 74dc2b253832e9cd6cab2f5c9e08d19d8a479b4b Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 6 Feb 2023 11:27:20 +0800 Subject: [PATCH 033/117] refactor remote menu Signed-off-by: fufesou --- .../lib/desktop/pages/remote_tab_page.dart | 93 +----- .../lib/desktop/widgets/remote_menubar.dart | 278 ++++++++++++------ 2 files changed, 207 insertions(+), 164 deletions(-) diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index d832db0c6..9b00b481f 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -243,96 +243,35 @@ class _ConnectionTabPageState extends State { padding: padding, ), MenuEntryDivider(), - MenuEntryRadios( - text: translate('Ratio'), - optionsGetter: () => [ - MenuEntryRadioOption( - text: translate('Scale original'), - value: kRemoteViewStyleOriginal, - dismissOnClicked: true, - ), - MenuEntryRadioOption( - text: translate('Scale adaptive'), - value: kRemoteViewStyleAdaptive, - dismissOnClicked: true, - ), - ], - curOptionGetter: () async => - // null means peer id is not found, which there's no need to care about - await bind.sessionGetViewStyle(id: key) ?? '', - optionSetter: (String oldValue, String newValue) async { - await bind.sessionSetViewStyle(id: key, value: newValue); - ffi.canvasModel.updateViewStyle(); - cancelFunc(); - }, - padding: padding, + RemoteMenuEntry.viewStyle( + key, + ffi, + padding, + dismissFunc: cancelFunc, ), ]); if (!ffi.canvasModel.cursorEmbedded) { menu.add(MenuEntryDivider()); - menu.add(() { - final state = ShowRemoteCursorState.find(key); - final optKey = 'show-remote-cursor'; - return MenuEntrySwitch2( - switchType: SwitchType.scheckbox, - text: translate('Show remote cursor'), - getter: () { - return state; - }, - setter: (bool v) async { - await bind.sessionToggleOption(id: key, value: optKey); - state.value = bind.sessionGetToggleOptionSync(id: key, arg: optKey); - cancelFunc(); - }, - padding: padding, - ); - }()); + menu.add(RemoteMenuEntry.showRemoteCursor( + key, + padding, + dismissFunc: cancelFunc, + )); } if (perms['keyboard'] != false) { if (perms['clipboard'] != false) { - menu.add(MenuEntrySwitch( - switchType: SwitchType.scheckbox, - text: translate('Disable clipboard'), - getter: () async { - return bind.sessionGetToggleOptionSync( - id: key, arg: 'disable-clipboard'); - }, - setter: (bool v) async { - await bind.sessionToggleOption(id: key, value: 'disable-clipboard'); - cancelFunc(); - }, - padding: padding, - )); + menu.add(RemoteMenuEntry.disableClipboard(key, padding, + dismissFunc: cancelFunc)); } - menu.add(MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Insert Lock'), - style: style, - ), - proc: () { - bind.sessionLockScreen(id: key); - cancelFunc(); - }, - padding: padding, - dismissOnClicked: true, - )); + menu.add( + RemoteMenuEntry.insertLock(key, padding, dismissFunc: cancelFunc)); if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) { - menu.add(MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - '${translate("Insert")} Ctrl + Alt + Del', - style: style, - ), - proc: () { - bind.sessionCtrlAltDel(id: key); - cancelFunc(); - }, - padding: padding, - dismissOnClicked: true, - )); + menu.add(RemoteMenuEntry.insertCtrlAltDel(key, padding, + dismissFunc: cancelFunc)); } } diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index d6b1cec72..36b9504c0 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -99,6 +99,175 @@ class _MenubarTheme { static const double dividerHeight = 12.0; } +typedef DismissFunc = void Function(); + +class RemoteMenuEntry { + static MenuEntryRadios viewStyle( + String remoteId, + FFI ffi, + EdgeInsets padding, { + DismissFunc? dismissFunc, + DismissCallback? dismissCallback, + RxString? rxViewStyle, + }) { + return MenuEntryRadios( + text: translate('Ratio'), + optionsGetter: () => [ + MenuEntryRadioOption( + text: translate('Scale original'), + value: kRemoteViewStyleOriginal, + dismissOnClicked: true, + dismissCallback: dismissCallback, + ), + MenuEntryRadioOption( + text: translate('Scale adaptive'), + value: kRemoteViewStyleAdaptive, + dismissOnClicked: true, + dismissCallback: dismissCallback, + ), + ], + curOptionGetter: () async { + // null means peer id is not found, which there's no need to care about + final viewStyle = await bind.sessionGetViewStyle(id: remoteId) ?? ''; + if (rxViewStyle != null) { + rxViewStyle.value = viewStyle; + } + return viewStyle; + }, + optionSetter: (String oldValue, String newValue) async { + await bind.sessionSetViewStyle(id: remoteId, value: newValue); + if (rxViewStyle != null) { + rxViewStyle.value = newValue; + } + ffi.canvasModel.updateViewStyle(); + if (dismissFunc != null) { + dismissFunc(); + } + }, + padding: padding, + dismissOnClicked: true, + dismissCallback: dismissCallback, + ); + } + + static MenuEntrySwitch2 showRemoteCursor( + String remoteId, + EdgeInsets padding, { + DismissFunc? dismissFunc, + DismissCallback? dismissCallback, + }) { + final state = ShowRemoteCursorState.find(remoteId); + final optKey = 'show-remote-cursor'; + return MenuEntrySwitch2( + switchType: SwitchType.scheckbox, + text: translate('Show remote cursor'), + getter: () { + return state; + }, + setter: (bool v) async { + await bind.sessionToggleOption(id: remoteId, value: optKey); + state.value = + bind.sessionGetToggleOptionSync(id: remoteId, arg: optKey); + if (dismissFunc != null) { + dismissFunc(); + } + }, + padding: padding, + dismissOnClicked: true, + dismissCallback: dismissCallback, + ); + } + + static MenuEntrySwitch disableClipboard( + String remoteId, + EdgeInsets? padding, { + DismissFunc? dismissFunc, + DismissCallback? dismissCallback, + }) { + return createSwitchMenuEntry( + remoteId, + 'Disable clipboard', + 'disable-clipboard', + padding, + true, + dismissCallback: dismissCallback, + ); + } + + static MenuEntrySwitch createSwitchMenuEntry( + String remoteId, + String text, + String option, + EdgeInsets? padding, + bool dismissOnClicked, { + DismissFunc? dismissFunc, + DismissCallback? dismissCallback, + }) { + return MenuEntrySwitch( + switchType: SwitchType.scheckbox, + text: translate(text), + getter: () async { + return bind.sessionGetToggleOptionSync(id: remoteId, arg: option); + }, + setter: (bool v) async { + await bind.sessionToggleOption(id: remoteId, value: option); + if (dismissFunc != null) { + dismissFunc(); + } + }, + padding: padding, + dismissOnClicked: dismissOnClicked, + dismissCallback: dismissCallback, + ); + } + + static MenuEntryButton insertLock( + String remoteId, + EdgeInsets? padding, { + DismissFunc? dismissFunc, + DismissCallback? dismissCallback, + }) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Insert Lock'), + style: style, + ), + proc: () { + bind.sessionLockScreen(id: remoteId); + if (dismissFunc != null) { + dismissFunc(); + } + }, + padding: padding, + dismissOnClicked: true, + dismissCallback: dismissCallback, + ); + } + + static insertCtrlAltDel( + String remoteId, + EdgeInsets? padding, { + DismissFunc? dismissFunc, + DismissCallback? dismissCallback, + }) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + '${translate("Insert")} Ctrl + Alt + Del', + style: style, + ), + proc: () { + bind.sessionCtrlAltDel(id: remoteId); + if (dismissFunc != null) { + dismissFunc(); + } + }, + padding: padding, + dismissOnClicked: true, + dismissCallback: dismissCallback, + ); + } +} + class RemoteMenubar extends StatefulWidget { final String id; final FFI ffi; @@ -616,18 +785,8 @@ class _RemoteMenubarState extends State { displayMenu.add(MenuEntryDivider()); if (perms['keyboard'] != false) { if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) { - displayMenu.add(MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - '${translate("Insert")} Ctrl + Alt + Del', - style: style, - ), - proc: () { - bind.sessionCtrlAltDel(id: widget.id); - }, - padding: padding, - dismissOnClicked: true, - dismissCallback: _menuDismissCallback, - )); + displayMenu.add(RemoteMenuEntry.insertCtrlAltDel(widget.id, padding, + dismissCallback: _menuDismissCallback)); } } if (perms['restart'] != false && @@ -649,18 +808,8 @@ class _RemoteMenubarState extends State { } if (perms['keyboard'] != false) { - displayMenu.add(MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Insert Lock'), - style: style, - ), - proc: () { - bind.sessionLockScreen(id: widget.id); - }, - padding: padding, - dismissOnClicked: true, - dismissCallback: _menuDismissCallback, - )); + displayMenu.add(RemoteMenuEntry.insertLock(widget.id, padding, + dismissCallback: _menuDismissCallback)); if (pi.platform == kPeerPlatformWindows) { displayMenu.add(MenuEntryButton( @@ -770,36 +919,12 @@ class _RemoteMenubarState extends State { const EdgeInsets padding = EdgeInsets.only(left: 18.0, right: 8.0); final peer_version = widget.ffi.ffiModel.pi.version; final displayMenu = [ - MenuEntryRadios( - text: translate('Ratio'), - optionsGetter: () => [ - MenuEntryRadioOption( - text: translate('Scale original'), - value: kRemoteViewStyleOriginal, - dismissOnClicked: true, - dismissCallback: _menuDismissCallback, - ), - MenuEntryRadioOption( - text: translate('Scale adaptive'), - value: kRemoteViewStyleAdaptive, - dismissOnClicked: true, - dismissCallback: _menuDismissCallback, - ), - ], - curOptionGetter: () async { - // null means peer id is not found, which there's no need to care about - final viewStyle = await bind.sessionGetViewStyle(id: widget.id) ?? ''; - widget.state.viewStyle.value = viewStyle; - return viewStyle; - }, - optionSetter: (String oldValue, String newValue) async { - await bind.sessionSetViewStyle(id: widget.id, value: newValue); - widget.state.viewStyle.value = newValue; - widget.ffi.canvasModel.updateViewStyle(); - }, - padding: padding, - dismissOnClicked: true, + RemoteMenuEntry.viewStyle( + widget.id, + widget.ffi, + padding, dismissCallback: _menuDismissCallback, + rxViewStyle: widget.state.viewStyle, ), MenuEntryDivider(), MenuEntryRadios( @@ -1158,25 +1283,11 @@ class _RemoteMenubarState extends State { /// Show remote cursor if (!widget.ffi.canvasModel.cursorEmbedded) { - displayMenu.add(() { - final state = ShowRemoteCursorState.find(widget.id); - final optKey = 'show-remote-cursor'; - return MenuEntrySwitch2( - switchType: SwitchType.scheckbox, - text: translate('Show remote cursor'), - getter: () { - return state; - }, - setter: (bool v) async { - await bind.sessionToggleOption(id: widget.id, value: optKey); - state.value = - bind.sessionGetToggleOptionSync(id: widget.id, arg: optKey); - }, - padding: padding, - dismissOnClicked: true, - dismissCallback: _menuDismissCallback, - ); - }()); + displayMenu.add(RemoteMenuEntry.showRemoteCursor( + widget.id, + padding, + dismissCallback: _menuDismissCallback, + )); } /// Show remote cursor scaling with image @@ -1237,8 +1348,11 @@ class _RemoteMenubarState extends State { if (perms['keyboard'] != false) { if (perms['clipboard'] != false) { - displayMenu.add(_createSwitchMenuEntry( - 'Disable clipboard', 'disable-clipboard', padding, true)); + displayMenu.add(RemoteMenuEntry.disableClipboard( + widget.id, + padding, + dismissCallback: _menuDismissCallback, + )); } displayMenu.add(_createSwitchMenuEntry( 'Lock after session end', 'lock-after-session-end', padding, true)); @@ -1349,19 +1463,9 @@ class _RemoteMenubarState extends State { MenuEntrySwitch _createSwitchMenuEntry( String text, String option, EdgeInsets? padding, bool dismissOnClicked) { - return MenuEntrySwitch( - switchType: SwitchType.scheckbox, - text: translate(text), - getter: () async { - return bind.sessionGetToggleOptionSync(id: widget.id, arg: option); - }, - setter: (bool v) async { - await bind.sessionToggleOption(id: widget.id, value: option); - }, - padding: padding, - dismissOnClicked: dismissOnClicked, - dismissCallback: _menuDismissCallback, - ); + return RemoteMenuEntry.createSwitchMenuEntry( + widget.id, text, option, padding, dismissOnClicked, + dismissCallback: _menuDismissCallback); } } From 40d0ea016bae6dca3b51f503f25003312f129299 Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 3 Feb 2023 15:07:45 +0800 Subject: [PATCH 034/117] refactor peer tab with model, make it scrollable Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_tab_page.dart | 449 ++++++------------ flutter/lib/main.dart | 1 + flutter/lib/mobile/widgets/dialog.dart | 6 +- flutter/lib/models/group_model.dart | 4 +- flutter/lib/models/model.dart | 5 +- flutter/lib/models/peer_tab_model.dart | 275 +++++++++++ flutter/lib/models/user_model.dart | 2 +- 7 files changed, 423 insertions(+), 319 deletions(-) create mode 100644 flutter/lib/models/peer_tab_model.dart diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 150121c59..4080f9c11 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -1,7 +1,7 @@ -import 'dart:convert'; import 'dart:ui' as ui; import 'package:bot_toast/bot_toast.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/address_book.dart'; import 'package:flutter_hbb/common/widgets/my_group.dart'; @@ -12,181 +12,15 @@ import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart' as mod_menu; +import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:get/get.dart'; +import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart'; +import 'package:provider/provider.dart'; +import 'package:visibility_detector/visibility_detector.dart'; import '../../common.dart'; import '../../models/platform_model.dart'; -const int groupTabIndex = 4; -const String defaultGroupTabname = 'Group'; - -class StatePeerTab { - final RxInt currentTab = 0.obs; // index in tabNames - final RxList visibleOrderedTabs = RxList.empty(growable: true); - List tabOrder = List.from([0, 1, 2, 3, 4]); // constant length - final RxInt tabHiddenFlag = 0.obs; - final RxList tabNames = [ - 'Recent Sessions', - 'Favorites', - 'Discovered', - 'Address Book', - defaultGroupTabname, - ].obs; - - StatePeerTab._() { - // init tabHiddenFlag - tabHiddenFlag.value = (int.tryParse( - bind.getLocalFlutterConfig(k: 'hidden-peer-card'), - radix: 2) ?? - 0); - var tabs = _notHiddenTabs(); - // remove dynamic tabs - tabs.remove(groupTabIndex); - // init tabOrder - try { - final conf = bind.getLocalFlutterConfig(k: 'peer-tab-order'); - if (conf.isNotEmpty) { - final json = jsonDecode(conf); - if (json is List) { - final List list = - json.map((e) => int.tryParse(e.toString()) ?? -1).toList(); - if (list.length == tabOrder.length && - tabOrder.every((e) => list.contains(e))) { - tabOrder = list; - } - } - } - } catch (e) { - debugPrintStack(label: '$e'); - } - // init visibleOrderedTabs - var tempList = tabOrder.toList(); - tempList.removeWhere((e) => !tabs.contains(e)); - visibleOrderedTabs.value = tempList; - // init currentTab - currentTab.value = - int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0; - if (!tabs.contains(currentTab.value)) { - if (tabs.isNotEmpty) { - currentTab.value = tabs[0]; - } else { - currentTab.value = 0; - } - } - } - static final StatePeerTab instance = StatePeerTab._(); - - // check dynamic tabs - check() { - tabOrder2visibleOrderedTabs(); - if (visibleOrderedTabs.contains(groupTabIndex) && - int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) == - groupTabIndex) { - currentTab.value = groupTabIndex; - } - if (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isNotEmpty) { - tabNames[groupTabIndex] = gFFI.userModel.groupName.value; - } else { - tabNames[groupTabIndex] = defaultGroupTabname; - } - } - - visibleOrderedTabs2TabOrder() { - var tmpTabOrder = visibleOrderedTabs.toList(); - var left = tabOrder.where((e) => !tmpTabOrder.contains(e)).toList(); - for (var t in left) { - _addTabInOrder(tmpTabOrder, t); - } - statePeerTab.tabOrder = tmpTabOrder; - bind.setLocalFlutterConfig(k: 'peer-tab-order', v: jsonEncode(tmpTabOrder)); - } - - tabOrder2visibleOrderedTabs() { - var visible = statePeerTab.visibleTabs(); - statePeerTab.visibleOrderedTabs.value = - statePeerTab.tabOrder.where((e) => visible.contains(e)).toList(); - } - - // return true if hide group card - bool filterGroupCard() { - if (gFFI.groupModel.users.isEmpty || - (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) { - return true; - } else { - return false; - } - } - - // return index array of tabNames - List visibleTabs() { - var v = List.empty(growable: true); - for (int i = 0; i < tabNames.length; i++) { - if (!_isTabHidden(i) && !_isTabFilter(i)) { - v.add(i); - } - } - return v; - } - - bool _isTabHidden(int tabindex) { - return tabHiddenFlag & (1 << tabindex) != 0; - } - - bool _isTabFilter(int tabIndex) { - if (tabIndex == groupTabIndex) { - return filterGroupCard(); - } - return false; - } - - List _notHiddenTabs() { - var v = List.empty(growable: true); - for (int i = 0; i < tabNames.length; i++) { - if (!_isTabHidden(i)) { - v.add(i); - } - } - return v; - } - - // add tabIndex to list - _addTabInOrder(List list, int tabIndex) { - if (!tabOrder.contains(tabIndex) || list.contains(tabIndex)) { - return; - } - bool sameOrder = true; - int lastIndex = -1; - for (int i = 0; i < list.length; i++) { - var index = tabOrder.lastIndexOf(list[i]); - if (index > lastIndex) { - lastIndex = index; - continue; - } else { - sameOrder = false; - break; - } - } - if (sameOrder) { - var indexInTabOrder = tabOrder.indexOf(tabIndex); - var left = List.empty(growable: true); - for (int i = 0; i < indexInTabOrder; i++) { - left.add(tabOrder[i]); - } - int insertIndex = list.lastIndexWhere((e) => left.contains(e)); - if (insertIndex < 0) { - insertIndex = 0; - } else { - insertIndex += 1; - } - list.insert(insertIndex, tabIndex); - } else { - list.add(tabIndex); - } - } -} - -final statePeerTab = StatePeerTab.instance; - class PeerTabPage extends StatefulWidget { const PeerTabPage({Key? key}) : super(key: key); @override @@ -232,11 +66,10 @@ class _PeerTabPageState extends State ), () => {}), ]; + final _scrollDebounce = Debouncer(delay: Duration(milliseconds: 50)); @override void initState() { - adjustTab(); - final uiType = bind.getLocalFlutterConfig(k: 'peer-card-ui-type'); if (uiType != '') { peerCardUiType.value = int.parse(uiType) == PeerUiType.list.index @@ -248,7 +81,7 @@ class _PeerTabPageState extends State Future handleTabSelection(int tabIndex) async { if (tabIndex < entries.length) { - statePeerTab.currentTab.value = tabIndex; + gFFI.peerTabModel.setCurrentTab(tabIndex); entries[tabIndex].load(); } } @@ -270,6 +103,7 @@ class _PeerTabPageState extends State Expanded( child: visibleContextMenuListener( _createSwitchBar(context))), + buildScrollJumper(), const PeerSearchBar(), Offstage( offstage: !isDesktop, @@ -284,98 +118,115 @@ class _PeerTabPageState extends State } Widget _createSwitchBar(BuildContext context) { - return Obx(() { - var tabs = statePeerTab.visibleOrderedTabs; - int indexCounter = -1; - return ReorderableListView( - buildDefaultDragHandles: false, - onReorder: (oldIndex, newIndex) { - if (oldIndex < newIndex) { - newIndex -= 1; - } - var list = tabs.toList(); - final int item = list.removeAt(oldIndex); - list.insert(newIndex, item); - tabs.value = list; - statePeerTab.visibleOrderedTabs2TabOrder(); - }, - scrollDirection: Axis.horizontal, - physics: NeverScrollableScrollPhysics(), - scrollController: ScrollController(), - children: tabs.map((t) { - indexCounter++; - return ReorderableDragStartListener( + final model = Provider.of(context); + int indexCounter = -1; + return ReorderableListView( + buildDefaultDragHandles: false, + onReorder: (oldIndex, newIndex) { + model.onReorder(oldIndex, newIndex); + }, + scrollDirection: Axis.horizontal, + physics: NeverScrollableScrollPhysics(), + scrollController: model.sc, + children: model.visibleOrderedTabs.map((t) { + indexCounter++; + return ReorderableDragStartListener( + key: ValueKey(t), + index: indexCounter, + child: VisibilityDetector( key: ValueKey(t), - index: indexCounter, - child: InkWell( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: statePeerTab.currentTab.value == t - ? Theme.of(context).backgroundColor - : null, - borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), - ), - child: Align( - alignment: Alignment.center, - child: Text( - translatedTabname(t), - textAlign: TextAlign.center, - style: TextStyle( - height: 1, - fontSize: 14, - color: statePeerTab.currentTab.value == t - ? MyTheme.tabbar(context).selectedTextColor - : MyTheme.tabbar(context).unSelectedTextColor - ?..withOpacity(0.5)), - ), - )), - onTap: () async { - await handleTabSelection(t); - await bind.setLocalFlutterConfig( - k: 'peer-tab-index', v: t.toString()); + onVisibilityChanged: (info) { + final id = (info.key as ValueKey).value; + model.setTabFullyVisible(id, info.visibleFraction > 0.99); + }, + child: Listener( + // handle mouse wheel + onPointerSignal: (e) { + if (e is PointerScrollEvent) { + if (!model.sc.canScroll) return; + _scrollDebounce.call(() { + model.sc.animateTo(model.sc.offset + e.scrollDelta.dy, + duration: Duration(milliseconds: 200), + curve: Curves.ease); + }); + } }, + child: InkWell( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: model.currentTab == t + ? Theme.of(context).backgroundColor + : null, + borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), + ), + child: Align( + alignment: Alignment.center, + child: Text( + model.translatedTabname(t), + textAlign: TextAlign.center, + style: TextStyle( + height: 1, + fontSize: 14, + color: model.currentTab == t + ? MyTheme.tabbar(context).selectedTextColor + : MyTheme.tabbar(context).unSelectedTextColor + ?..withOpacity(0.5)), + ), + )), + onTap: () async { + await handleTabSelection(t); + await bind.setLocalFlutterConfig( + k: 'peer-tab-index', v: t.toString()); + }, + ), ), - ); - }).toList()); - }); + ), + ); + }).toList()); } - translatedTabname(int index) { - if (index < statePeerTab.tabNames.length) { - final name = statePeerTab.tabNames[index]; - if (index == groupTabIndex) { - if (name == defaultGroupTabname) { - return translate(name); - } else { - return name; - } - } else { - return translate(name); - } - } - assert(false); - return index.toString(); + Widget buildScrollJumper() { + final model = Provider.of(context); + return Offstage( + offstage: !model.showScrollBtn, + child: Row( + children: [ + GestureDetector( + child: Icon(Icons.arrow_left, + size: 22, + color: model.leftFullyVisible + ? Theme.of(context).disabledColor + : null), + onTap: model.sc.backward), + GestureDetector( + child: Icon(Icons.arrow_right, + size: 22, + color: model.rightFullyVisible + ? Theme.of(context).disabledColor + : null), + onTap: model.sc.forward) + ], + )); } Widget _createPeersView() { - final verticalMargin = isDesktop ? 12.0 : 6.0; - return Expanded( - child: Obx(() { - var tabs = statePeerTab.visibleOrderedTabs; - if (tabs.isEmpty) { - return visibleContextMenuListener(Center( - child: Text(translate('Right click to select tabs')), - )); + final model = Provider.of(context); + Widget child; + if (model.visibleOrderedTabs.isEmpty) { + child = visibleContextMenuListener(Center( + child: Text(translate('Right click to select tabs')), + )); + } else { + if (model.visibleOrderedTabs.contains(model.currentTab)) { + child = entries[model.currentTab].widget; } else { - if (tabs.contains(statePeerTab.currentTab.value)) { - return entries[statePeerTab.currentTab.value].widget; - } else { - statePeerTab.currentTab.value = tabs[0]; - return entries[statePeerTab.currentTab.value].widget; - } + model.setCurrentTab(model.visibleOrderedTabs[0]); + child = entries[0].widget; } - }).marginSymmetric(vertical: verticalMargin)); + } + return Expanded( + child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0)); } Widget _createPeerViewTypeSwitch(BuildContext context) { @@ -408,13 +259,6 @@ class _PeerTabPageState extends State ); } - adjustTab() { - var tabs = statePeerTab.visibleOrderedTabs; - if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) { - statePeerTab.currentTab.value = tabs[0]; - } - } - Widget visibleContextMenuListener(Widget child) { return Listener( onPointerDown: (e) { @@ -434,55 +278,36 @@ class _PeerTabPageState extends State } Widget visibleContextMenu(CancelFunc cancelFunc) { - return Obx(() { - final List menu = List.empty(growable: true); - final List menuIndex = List.empty(growable: true); - for (int i = 0; i < statePeerTab.tabNames.length; i++) { - if (i == groupTabIndex && statePeerTab.filterGroupCard()) { - continue; - } - int bitMask = 1 << i; - menuIndex.add(i); - menu.add(MenuEntrySwitch( - switchType: SwitchType.scheckbox, - text: translatedTabname(i), - getter: () async { - return statePeerTab.tabHiddenFlag & bitMask == 0; - }, - setter: (show) async { - if (show) { - statePeerTab.tabHiddenFlag.value &= ~bitMask; - } else { - statePeerTab.tabHiddenFlag.value |= bitMask; - } - await bind.setLocalFlutterConfig( - k: 'hidden-peer-card', - v: statePeerTab.tabHiddenFlag.value.toRadixString(2)); - statePeerTab.tabOrder2visibleOrderedTabs(); - cancelFunc(); - adjustTab(); - })); - } - // show in tabOrder - List menu2 = List.empty(growable: true); - statePeerTab.tabOrder.map((e) { - final index = menuIndex.indexOf(e); - if (index >= 0) { - menu2.add(menu[index]); - } - }).toList(); - return mod_menu.PopupMenu( - items: menu2 - .map((entry) => entry.build( - context, - const MenuConfig( - commonColor: MyTheme.accent, - height: 20.0, - dividerHeight: 12.0, - ))) - .expand((i) => i) - .toList()); - }); + final model = Provider.of(context); + final List menu = List.empty(growable: true); + final List menuIndex = List.empty(growable: true); + var list = model.orderedNotFilteredTabs(); + for (int i = 0; i < list.length; i++) { + int tabIndex = list[i]; + int bitMask = 1 << tabIndex; + menuIndex.add(tabIndex); + menu.add(MenuEntrySwitch( + switchType: SwitchType.scheckbox, + text: model.translatedTabname(tabIndex), + getter: () async { + return model.tabHiddenFlag & bitMask == 0; + }, + setter: (show) async { + model.onHideShow(tabIndex, show); + cancelFunc(); + })); + } + return mod_menu.PopupMenu( + items: menu + .map((entry) => entry.build( + context, + const MenuConfig( + commonColor: MyTheme.accent, + height: 20.0, + dividerHeight: 12.0, + ))) + .expand((i) => i) + .toList()); } } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 86cc9d89b..a2ae959c0 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -353,6 +353,7 @@ class _AppState extends State { ChangeNotifierProvider.value(value: gFFI.imageModel), ChangeNotifierProvider.value(value: gFFI.cursorModel), ChangeNotifierProvider.value(value: gFFI.canvasModel), + ChangeNotifierProvider.value(value: gFFI.peerTabModel), ], child: GetMaterialApp( navigatorKey: globalKey, diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index 2fbe40091..7e9a9879c 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -643,9 +643,9 @@ class _PasswordWidgetState extends State { // Here is key idea suffixIcon: IconButton( icon: Icon( - // Based on passwordVisible state choose the icon - _passwordVisible ? Icons.visibility : Icons.visibility_off, - ), + // Based on passwordVisible state choose the icon + _passwordVisible ? Icons.visibility : Icons.visibility_off, + color: MyTheme.lightTheme.primaryColor), onPressed: () { // Update the state i.e. toggle the state of passwordVisible variable setState(() { diff --git a/flutter/lib/models/group_model.dart b/flutter/lib/models/group_model.dart index 4d9fab0e4..5e2b85f90 100644 --- a/flutter/lib/models/group_model.dart +++ b/flutter/lib/models/group_model.dart @@ -35,7 +35,7 @@ class GroupModel { await reset(); if (gFFI.userModel.userName.isEmpty || (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) { - statePeerTab.check(); + gFFI.peerTabModel.check_dynamic_tabs(); return; } userLoading.value = true; @@ -82,7 +82,7 @@ class GroupModel { userLoadError.value = err.toString(); } finally { userLoading.value = false; - statePeerTab.check(); + gFFI.peerTabModel.check_dynamic_tabs(); } } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index aae4c6a07..daf7bfe34 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -13,6 +13,7 @@ import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_hbb/models/group_model.dart'; +import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/user_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; @@ -1292,8 +1293,9 @@ class FFI { late final AbModel abModel; // global late final GroupModel groupModel; // global late final UserModel userModel; // global + late final PeerTabModel peerTabModel; // global late final QualityMonitorModel qualityMonitorModel; // session - late final RecordingModel recordingModel; // recording + late final RecordingModel recordingModel; // session late final InputModel inputModel; // session FFI() { @@ -1305,6 +1307,7 @@ class FFI { chatModel = ChatModel(WeakReference(this)); fileModel = FileModel(WeakReference(this)); userModel = UserModel(WeakReference(this)); + peerTabModel = PeerTabModel(WeakReference(this)); abModel = AbModel(WeakReference(this)); groupModel = GroupModel(WeakReference(this)); qualityMonitorModel = QualityMonitorModel(WeakReference(this)); diff --git a/flutter/lib/models/peer_tab_model.dart b/flutter/lib/models/peer_tab_model.dart new file mode 100644 index 000000000..7c6211682 --- /dev/null +++ b/flutter/lib/models/peer_tab_model.dart @@ -0,0 +1,275 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:get/get.dart'; +import 'package:scroll_pos/scroll_pos.dart'; + +import '../common.dart'; +import 'model.dart'; + +const int groupTabIndex = 4; +const String defaultGroupTabname = 'Group'; + +class PeerTabModel with ChangeNotifier { + WeakReference parent; + int get currentTab => _currentTab; + int _currentTab = 0; // index in tabNames + List get visibleOrderedTabs => _visibleOrderedTabs; + List _visibleOrderedTabs = List.empty(growable: true); + List get tabOrder => _tabOrder; + List _tabOrder = List.from([0, 1, 2, 3, 4]); // constant length + int get tabHiddenFlag => _tabHiddenFlag; + int _tabHiddenFlag = 0; + bool get showScrollBtn => _showScrollBtn; + bool _showScrollBtn = false; + final List _fullyVisible = List.filled(5, false); + bool get leftFullyVisible => _leftFullyVisible; + bool _leftFullyVisible = false; + bool get rightFullyVisible => _rightFullyVisible; + bool _rightFullyVisible = false; + ScrollPosController sc = ScrollPosController(); + List tabNames = [ + 'Recent Sessions', + 'Favorites', + 'Discovered', + 'Address Book', + defaultGroupTabname, + ]; + + PeerTabModel(this.parent) { + // init tabHiddenFlag + _tabHiddenFlag = int.tryParse( + bind.getLocalFlutterConfig(k: 'hidden-peer-card'), + radix: 2) ?? + 0; + var tabs = _notHiddenTabs(); + // remove dynamic tabs + tabs.remove(groupTabIndex); + // init tabOrder + try { + final conf = bind.getLocalFlutterConfig(k: 'peer-tab-order'); + if (conf.isNotEmpty) { + final json = jsonDecode(conf); + if (json is List) { + final List list = + json.map((e) => int.tryParse(e.toString()) ?? -1).toList(); + if (list.length == _tabOrder.length && + _tabOrder.every((e) => list.contains(e))) { + _tabOrder = list; + } + } + } + } catch (e) { + debugPrintStack(label: '$e'); + } + // init visibleOrderedTabs + var tempList = _tabOrder.toList(); + tempList.removeWhere((e) => !tabs.contains(e)); + _visibleOrderedTabs = tempList; + // init currentTab + _currentTab = + int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0; + if (!tabs.contains(_currentTab)) { + if (tabs.isNotEmpty) { + _currentTab = tabs[0]; + } else { + _currentTab = 0; + } + } + sc.itemCount = _visibleOrderedTabs.length; + } + + check_dynamic_tabs() { + var visible = visibleTabs(); + _visibleOrderedTabs = _tabOrder.where((e) => visible.contains(e)).toList(); + if (_visibleOrderedTabs.contains(groupTabIndex) && + int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) == + groupTabIndex) { + _currentTab = groupTabIndex; + } + if (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isNotEmpty) { + tabNames[groupTabIndex] = gFFI.userModel.groupName.value; + } else { + tabNames[groupTabIndex] = defaultGroupTabname; + } + sc.itemCount = _visibleOrderedTabs.length; + notifyListeners(); + } + + setCurrentTab(int index) { + if (_currentTab != index) { + _currentTab = index; + notifyListeners(); + } + } + + setTabFullyVisible(int index, bool visible) { + if (index >= 0 && index < _fullyVisible.length) { + if (visible != _fullyVisible[index]) { + _fullyVisible[index] = visible; + bool changed = false; + bool show = _visibleOrderedTabs.any((e) => !_fullyVisible[e]); + if (show != _showScrollBtn) { + _showScrollBtn = show; + changed = true; + } + if (_visibleOrderedTabs.isNotEmpty && _visibleOrderedTabs[0] == index) { + if (_leftFullyVisible != visible) { + _leftFullyVisible = visible; + changed = true; + } + } + if (_visibleOrderedTabs.isNotEmpty && + _visibleOrderedTabs.last == index) { + if (_rightFullyVisible != visible) { + _rightFullyVisible = visible; + changed = true; + } + } + if (changed) { + notifyListeners(); + } + } + } + } + + onReorder(oldIndex, newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + var list = _visibleOrderedTabs.toList(); + final int item = list.removeAt(oldIndex); + list.insert(newIndex, item); + _visibleOrderedTabs = list; + + var tmpTabOrder = _visibleOrderedTabs.toList(); + var left = _tabOrder.where((e) => !tmpTabOrder.contains(e)).toList(); + for (var t in left) { + _addTabInOrder(tmpTabOrder, t); + } + _tabOrder = tmpTabOrder; + bind.setLocalFlutterConfig(k: 'peer-tab-order', v: jsonEncode(tmpTabOrder)); + notifyListeners(); + } + + onHideShow(int index, bool show) async { + int bitMask = 1 << index; + if (show) { + _tabHiddenFlag &= ~bitMask; + } else { + _tabHiddenFlag |= bitMask; + } + await bind.setLocalFlutterConfig( + k: 'hidden-peer-card', v: _tabHiddenFlag.toRadixString(2)); + var visible = visibleTabs(); + _visibleOrderedTabs = _tabOrder.where((e) => visible.contains(e)).toList(); + if (_visibleOrderedTabs.isNotEmpty && + !_visibleOrderedTabs.contains(_currentTab)) { + _currentTab = _visibleOrderedTabs[0]; + } + notifyListeners(); + } + + List orderedNotFilteredTabs() { + var list = tabOrder.toList(); + if (_filterGroupCard()) { + list.remove(groupTabIndex); + } + return list; + } + + // return index array of tabNames + List visibleTabs() { + var v = List.empty(growable: true); + for (int i = 0; i < tabNames.length; i++) { + if (!_isTabHidden(i) && !_isTabFilter(i)) { + v.add(i); + } + } + return v; + } + + String translatedTabname(int index) { + if (index >= 0 && index < tabNames.length) { + final name = tabNames[index]; + if (index == groupTabIndex) { + if (name == defaultGroupTabname) { + return translate(name); + } else { + return name; + } + } else { + return translate(name); + } + } + assert(false); + return index.toString(); + } + + bool _isTabHidden(int tabindex) { + return _tabHiddenFlag & (1 << tabindex) != 0; + } + + bool _isTabFilter(int tabIndex) { + if (tabIndex == groupTabIndex) { + return _filterGroupCard(); + } + return false; + } + + // return true if hide group card + bool _filterGroupCard() { + if (gFFI.groupModel.users.isEmpty || + (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) { + return true; + } else { + return false; + } + } + + List _notHiddenTabs() { + var v = List.empty(growable: true); + for (int i = 0; i < tabNames.length; i++) { + if (!_isTabHidden(i)) { + v.add(i); + } + } + return v; + } + + // add tabIndex to list + _addTabInOrder(List list, int tabIndex) { + if (!_tabOrder.contains(tabIndex) || list.contains(tabIndex)) { + return; + } + bool sameOrder = true; + int lastIndex = -1; + for (int i = 0; i < list.length; i++) { + var index = _tabOrder.lastIndexOf(list[i]); + if (index > lastIndex) { + lastIndex = index; + continue; + } else { + sameOrder = false; + break; + } + } + if (sameOrder) { + var indexInTabOrder = _tabOrder.indexOf(tabIndex); + var left = List.empty(growable: true); + for (int i = 0; i < indexInTabOrder; i++) { + left.add(_tabOrder[i]); + } + int insertIndex = list.lastIndexWhere((e) => left.contains(e)); + if (insertIndex < 0) { + insertIndex = 0; + } else { + insertIndex += 1; + } + list.insert(insertIndex, tabIndex); + } else { + list.add(tabIndex); + } + } +} diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index 6694d8c5c..7f40b3333 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -62,7 +62,7 @@ class UserModel { await gFFI.groupModel.reset(); userName.value = ''; groupName.value = ''; - statePeerTab.check(); + gFFI.peerTabModel.check_dynamic_tabs(); } Future _parseAndUpdateUser(UserPayload user) async { From b2afde4b27e82944250143304529210e6d6ad5aa Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 7 Feb 2023 00:18:25 +0800 Subject: [PATCH 035/117] tmp workaround of '-cm' not exit cause rustdesk not launchable from finder --- src/flutter.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/flutter.rs b/src/flutter.rs index d8f83c6b2..761f8a612 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -39,7 +39,8 @@ pub extern "C" fn rustdesk_core_main() -> bool { #[no_mangle] pub extern "C" fn handle_applicationShouldOpenUntitledFile() { hbb_common::log::debug!("icon clicked on finder"); - if std::env::args().nth(1) == Some("--server".to_owned()) { + let x = std::env::args().nth(1).unwrap_or_default(); + if x == "--server" || x == "--cm" { crate::platform::macos::check_main_window(); } } From 1426771ec9cb208ec5d5e06fe643ea2bf0852f1d Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 01:31:11 +0800 Subject: [PATCH 036/117] fix: uni links failed to be invoked with --cm running on macOS --- flutter/lib/common.dart | 16 +++++++++++++--- flutter/lib/main.dart | 8 +++++++- flutter/lib/models/native_model.dart | 2 +- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 8236597ff..41043069a 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1292,14 +1292,24 @@ Future initUniLinks() async { } } -StreamSubscription? listenUniLinks() { - if (!(Platform.isWindows || Platform.isMacOS)) { +/// Listen for uni links. +/// +/// * handleByFlutter: Should uni links being handled by Flutter. +/// +/// Returns a [StreamSubscription] which can listen the uni links. +StreamSubscription? listenUniLinks({handleByFlutter = true}) { + if (Platform.isLinux) { return null; } final sub = uriLinkStream.listen((Uri? uri) { + debugPrint("A uri was received: $uri."); if (uri != null) { - callUniLinksUriHandler(uri); + if (handleByFlutter) { + callUniLinksUriHandler(uri); + } else { + bind.sendUrlScheme(url: uri.toString()); + } } else { print("uni listen error: uri is empty."); } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index a2ae959c0..cc40d962f 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -31,6 +32,9 @@ int? kWindowId; WindowType? kWindowType; late List kBootArgs; +/// Uni links. +StreamSubscription? _uniLinkSubscription; + Future main(List args) async { WidgetsFlutterBinding.ensureInitialized(); debugPrint("launch args: $args"); @@ -203,7 +207,7 @@ void runMultiWindow( await restoreWindowPosition(WindowType.PortForward, windowId: kWindowId!); break; default: - // no such appType + // no such appType exit(0); } // show window from hidden status @@ -222,6 +226,8 @@ void runConnectionManagerScreen(bool hide) async { } else { showCmWindow(); } + // Start the uni links handler and redirect links to Native, not for Flutter. + _uniLinkSubscription = listenUniLinks(handleByFlutter: false); } void showCmWindow() { diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index d6885bfb0..628bf502d 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -117,7 +117,7 @@ class PlatformFFI { if (Platform.isLinux) { // Start a dbus service, no need to await _ffiBind.mainStartDbusServer(); - } else if (Platform.isMacOS) { + } else if (Platform.isMacOS && isMain) { // Start an ipc server for handling url schemes. _ffiBind.mainStartIpcUrlServer(); } From 9d391d3801b802b5885ed1ab35af9bb01670e07c Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 01:35:38 +0800 Subject: [PATCH 037/117] opt: format and name --- flutter/lib/common.dart | 2 +- flutter/lib/main.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 41043069a..30d38b8db 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1294,7 +1294,7 @@ Future initUniLinks() async { /// Listen for uni links. /// -/// * handleByFlutter: Should uni links being handled by Flutter. +/// * handleByFlutter: Should uni links be handled by Flutter. /// /// Returns a [StreamSubscription] which can listen the uni links. StreamSubscription? listenUniLinks({handleByFlutter = true}) { diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index cc40d962f..c19adf753 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -207,7 +207,7 @@ void runMultiWindow( await restoreWindowPosition(WindowType.PortForward, windowId: kWindowId!); break; default: - // no such appType + // no such appType exit(0); } // show window from hidden status From 564b35d4c2a0bd9266c8b76f3eb6f6d6293e2a41 Mon Sep 17 00:00:00 2001 From: Andrzej Rudnik Date: Mon, 6 Feb 2023 21:29:36 +0100 Subject: [PATCH 038/117] Update pl.rs --- src/lang/pl.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/lang/pl.rs b/src/lang/pl.rs index daf4a7846..b7ccbdbb1 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -270,8 +270,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("OPEN", "Otwórz"), ("Chat", "Czat"), ("Total", "Łącznie"), - ("items", "elementy"), - ("Selected", "Zaznaczone"), + ("items", "elementów"), + ("Selected", "Zaznaczonych"), ("Screen Capture", "Przechwytywanie ekranu"), ("Input Control", "Kontrola wejścia"), ("Audio Capture", "Przechwytywanie dźwięku"), @@ -345,7 +345,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Dark Theme", "Ciemny motyw"), ("Dark", "Ciemny"), ("Light", "Jasny"), - ("Follow System", "Zgodne z systemem"), + ("Follow System", "Zgodny z systemem"), ("Enable hardware codec", "Włącz akcelerację sprzętową kodeków"), ("Unlock Security Settings", "Odblokuj ustawienia zabezpieczeń"), ("Enable Audio", "Włącz dźwięk"), @@ -392,7 +392,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("or", "albo"), ("Continue with", "Kontynuuj z"), ("Elevate", "Uzyskaj uprawnienia"), - ("Zoom cursor", "Zoom kursora"), + ("Zoom cursor", "Powiększenie kursora"), ("Accept sessions via password", "Uwierzytelnij sesję używając hasła"), ("Accept sessions via click", "Uwierzytelnij sesję poprzez kliknięcie"), ("Accept sessions via both", "Uwierzytelnij sesję za pomocą obu sposobów"), @@ -407,7 +407,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Right click to select tabs", "Kliknij prawym przyciskiem myszy by wybrać zakładkę"), ("Skipped", "Pominięte"), ("Add to Address Book", "Dodaj do Książki Adresowej"), - ("Group", "Grypy"), + ("Group", "Grupy"), ("Search", "Szukaj"), ("Closed manually by web console", "Zakończone manualnie z konsoli Web"), ("Local keyboard type", "Lokalny typ klawiatury"), @@ -433,17 +433,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Weak", "Słabe"), ("Medium", "Średnie"), ("Strong", "Mocne"), - ("Switch Sides", "Zmień Strony"), + ("Switch Sides", "Zamień Strony"), ("Please confirm if you want to share your desktop?", "Czy na pewno chcesz udostępnić swój ekran?"), ("Closed as expected", "Zamknięto pomyślnie"), - ("Display", ""), - ("Default View Style", ""), - ("Default Scroll Style", ""), - ("Default Image Quality", ""), - ("Default Codec", ""), - ("Bitrate", ""), - ("FPS", ""), - ("Auto", ""), - ("Other Default Options", ""), + ("Display", "Wyświetlanie"), + ("Default View Style", "Domyślny styl wyświetlania"), + ("Default Scroll Style", "Domyślny styl przewijania"), + ("Default Image Quality", "Domyślna jakość obrazu"), + ("Default Codec", "Dokyślny kodek"), + ("Bitrate", "Bitrate"), + ("FPS", "FPS"), + ("Auto", "Auto"), + ("Other Default Options", "Inne opcje domyślne"), ].iter().cloned().collect(); } From bcbc1573aa9f13114987dba9015a411f08fb8d59 Mon Sep 17 00:00:00 2001 From: jimmyGALLAND <64364019+jimmyGALLAND@users.noreply.github.com> Date: Mon, 6 Feb 2023 22:45:27 +0100 Subject: [PATCH 039/117] Update fr.rs --- src/lang/fr.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 19b932d2f..3b7f23ab9 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -63,7 +63,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Skip", "Ignorer"), ("Close", "Fermer"), ("Retry", "Réessayer"), - ("OK", "Confirmer"), + ("OK", "Valider"), ("Password Required", "Mot de passe requis"), ("Please enter your password", "Veuillez saisir votre mot de passe"), ("Remember password", "Mémoriser le mot de passe"), @@ -126,7 +126,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("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"), - ("Insert", "Insérer"), + ("Insert", "Envoyer"), ("Insert Lock", "Verrouiller l'ordinateur distant"), ("Refresh", "Rafraîchir l'écran"), ("ID does not exist", "L'ID n'existe pas"), @@ -291,7 +291,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Écraser"), ("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"), ("Quit", "Quitter"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), + ("doc_mac_permission", "https://rustdesk.com/docs/fr/manual/mac/#enable-permissions"), ("Help", "Aider"), ("Failed", "échouer"), ("Succeeded", "Succès"), @@ -435,15 +435,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Strong", "Fort"), ("Switch Sides", "Inverser la prise de contrôle"), ("Please confirm if you want to share your desktop?", "Veuillez confirmer le partager de votre bureau ?"), - ("Closed as expected", ""), - ("Display", ""), - ("Default View Style", ""), - ("Default Scroll Style", ""), - ("Default Image Quality", ""), - ("Default Codec", ""), - ("Bitrate", ""), - ("FPS", ""), - ("Auto", ""), - ("Other Default Options", ""), + ("Closed as expected", "Fermé normalement"), + ("Display", "Affichage"), + ("Default View Style", "Style de vue par défaut"), + ("Default Scroll Style", "Style de défilement par défaut"), + ("Default Image Quality", "Qualité d'image par défaut"), + ("Default Codec", "Codec par défaut"), + ("Bitrate", "Débit"), + ("FPS", "FPS"), + ("Auto", "Auto"), + ("Other Default Options", "Autres options par défaut"), ].iter().cloned().collect(); } From e1a9cfcf7f841e57715709f8adcd6407a2e1062b Mon Sep 17 00:00:00 2001 From: 21pages Date: Tue, 7 Feb 2023 12:47:07 +0800 Subject: [PATCH 040/117] fix flink Signed-off-by: 21pages --- src/server/portable_service.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/server/portable_service.rs b/src/server/portable_service.rs index a2f6fb829..c783fef52 100644 --- a/src/server/portable_service.rs +++ b/src/server/portable_service.rs @@ -118,11 +118,9 @@ impl SharedMemory { fn flink(name: String) -> ResultType { let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string()); - let mut dir = PathBuf::from(disk); - let dir1 = dir.join("ProgramData"); - let dir2 = std::env::var("TEMP") - .map(|d| PathBuf::from(d)) - .unwrap_or(dir.join("Windows").join("Temp")); + let dir1 = PathBuf::from(format!("{}\\ProgramData", disk)); + let dir2 = PathBuf::from(format!("{}\\Windows\\Temp", disk)); + let mut dir; if dir1.exists() { dir = dir1; } else if dir2.exists() { From cf3ddb2a183bcfdd506942fde57f69c36058756b Mon Sep 17 00:00:00 2001 From: 21pages Date: Tue, 7 Feb 2023 15:16:49 +0800 Subject: [PATCH 041/117] filter foreground window to avoid frequent prompts Signed-off-by: 21pages --- src/platform/windows.rs | 31 ++++++++++++++++++++++++++++++- src/server/video_service.rs | 5 +---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 2e0d56eab..17f275c2a 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -11,6 +11,7 @@ use std::io::prelude::*; use std::{ ffi::OsString, fs, io, mem, + os::windows::process::CommandExt, path::PathBuf, sync::{Arc, Mutex}, time::{Duration, Instant}, @@ -1644,6 +1645,29 @@ pub fn is_elevated(process_id: Option) -> ResultType { } } +#[inline] +fn filter_foreground_window(process_id: DWORD) -> ResultType { + if let Ok(output) = std::process::Command::new("tasklist") + .args(vec![ + "/SVC", + "/NH", + "/FI", + &format!("PID eq {}", process_id), + ]) + .creation_flags(CREATE_NO_WINDOW) + .output() + { + let s = String::from_utf8_lossy(&output.stdout) + .to_string() + .to_lowercase(); + Ok(["Taskmgr", "mmc", "regedit"] + .iter() + .any(|name| s.contains(&name.to_string().to_lowercase()))) + } else { + bail!("run tasklist failed"); + } +} + pub fn is_foreground_window_elevated() -> ResultType { unsafe { let mut process_id: DWORD = 0; @@ -1651,7 +1675,12 @@ pub fn is_foreground_window_elevated() -> ResultType { if process_id == 0 { bail!("Failed to get processId, errno {}", GetLastError()) } - is_elevated(Some(process_id)) + let elevated = is_elevated(Some(process_id))?; + if elevated { + filter_foreground_window(process_id) + } else { + Ok(false) + } } } diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 55920e320..57fdf2c22 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -954,10 +954,7 @@ pub fn get_current_display() -> ResultType<(usize, usize, Display)> { fn start_uac_elevation_check() { static START: Once = Once::new(); START.call_once(|| { - if !crate::platform::is_installed() - && !crate::platform::is_root() - && !crate::portable_service::client::running() - { + if !crate::platform::is_installed() && !crate::platform::is_root() { std::thread::spawn(|| loop { std::thread::sleep(std::time::Duration::from_secs(1)); if let Ok(uac) = crate::ui::win_privacy::is_process_consent_running() { From 8aba51c1202e45067d1fc2a541cc096fccf5d4d4 Mon Sep 17 00:00:00 2001 From: 21pages Date: Tue, 7 Feb 2023 15:39:46 +0800 Subject: [PATCH 042/117] fix cm push_event Signed-off-by: 21pages --- src/flutter.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/flutter.rs b/src/flutter.rs index 761f8a612..b4f1f6bc6 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -14,6 +14,7 @@ use std::{ }; pub(super) const APP_TYPE_MAIN: &str = "main"; +pub(super) const APP_TYPE_CM: &str = "cm"; pub(super) const APP_TYPE_DESKTOP_REMOTE: &str = "remote"; pub(super) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer"; pub(super) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward"; @@ -528,11 +529,7 @@ pub mod connection_manager { assert!(h.get("name").is_none()); h.insert("name", name); - if let Some(s) = GLOBAL_EVENT_STREAM - .read() - .unwrap() - .get(super::APP_TYPE_MAIN) - { + if let Some(s) = GLOBAL_EVENT_STREAM.read().unwrap().get(super::APP_TYPE_CM) { s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned())); }; } From e0f73ccc28e2f85cb999d03281f29d2fdaf6280d Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 7 Feb 2023 16:17:00 +0800 Subject: [PATCH 043/117] remove docs/SECURITY.md to disable security report which is not the report we imagine --- docs/SECURITY.md | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 docs/SECURITY.md diff --git a/docs/SECURITY.md b/docs/SECURITY.md deleted file mode 100644 index f1114f913..000000000 --- a/docs/SECURITY.md +++ /dev/null @@ -1,13 +0,0 @@ -# Security Policy - -## Supported Versions - -| Version | Supported | -| --------- | ------------------ | -| 1.1.x | :white_check_mark: | -| 1.x | :white_check_mark: | -| Below 1.0 | :x: | - -## Reporting a Vulnerability - -Here we should write what to do in case of a security vulnerability From 28ad271693c4ba550d41b82a68a7a90d392118dc Mon Sep 17 00:00:00 2001 From: Kingtous Date: Thu, 3 Nov 2022 21:09:37 +0800 Subject: [PATCH 044/117] wip: dual audio transmission server --- src/client/io_loop.rs | 54 +++++++++++++++++++++++++++++++++++++++- src/server.rs | 21 ++++++++++++++++ src/server/connection.rs | 6 +++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 0178fe9e8..bcbea994b 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -2,13 +2,16 @@ use crate::client::{ Client, CodecFormat, MediaData, MediaSender, QualityStatus, MILLI1, SEC30, SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED, SERVER_KEYBOARD_ENABLED, }; -use crate::common; #[cfg(not(any(target_os = "android", target_os = "ios")))] use crate::common::{check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL}; +use crate::{audio_service, common, ConnInner, CLIENT_SERVER}; #[cfg(windows)] use clipboard::{cliprdr::CliprdrClientContext, ContextSend}; +use hbb_common::futures::channel::mpsc::unbounded; +use hbb_common::tokio::sync::mpsc::error::TryRecvError; +use crate::server::Service; use crate::ui_session_interface::{InvokeUiSession, Session}; use crate::{client::Data, client::Interface}; @@ -253,6 +256,55 @@ impl Remote { } } + // Start a local audio recorder, records audio and send to remote + fn start_client_audio( + &mut self, + audio_sender: MediaSender, + ) -> Option> { + if self.handler.is_file_transfer() || self.handler.is_port_forward() { + return None; + } + // Create a channel to receive error or closed message + let (tx, rx) = std::sync::mpsc::channel(); + let (tx_audio_data, mut rx_audio_data) = hbb_common::tokio::sync::mpsc::unbounded_channel(); + // Create a stand-alone inner, add subscribe to audio service + let client_conn_inner = ConnInner::new( + CLIENT_SERVER.write().unwrap().get_new_id(), + Some(tx_audio_data), + None, + ); + CLIENT_SERVER + .write() + .unwrap() + .subscribe(audio_service::NAME, client_conn_inner, true); + std::thread::spawn(move || { + loop { + // check if client is closed + match rx.try_recv() { + Ok(_) | Err(std::sync::mpsc::TryRecvError::Disconnected) => { + log::debug!("Exit local audio service of client"); + break; + } + _ => {} + } + match rx_audio_data.try_recv() { + Ok((instant, msg)) => match msg.union { + Some(_) => todo!(), + None => todo!(), + }, + Err(err) => { + if err == TryRecvError::Empty { + // ignore + } else { + log::debug!("Failed to record local audio channel: {}", err); + } + } + } + } + }); + Some(tx) + } + fn start_clipboard(&mut self) -> Option> { if self.handler.is_file_transfer() || self.handler.is_port_forward() { return None; diff --git a/src/server.rs b/src/server.rs index 109fc1e9a..bef49f132 100644 --- a/src/server.rs +++ b/src/server.rs @@ -29,6 +29,13 @@ use service::{GenericService, Service, Subscriber}; use service::ServiceTmpl; use crate::ipc::{connect, Data}; +pub use service::{GenericService, Service, ServiceTmpl, Subscriber}; +use std::{ + collections::HashMap, + net::SocketAddr, + sync::{Arc, Mutex, RwLock, Weak}, + time::Duration, +}; pub mod audio_service; cfg_if::cfg_if! { @@ -65,6 +72,13 @@ type ConnMap = HashMap; lazy_static::lazy_static! { pub static ref CHILD_PROCESS: Childs = Default::default(); pub static ref CONN_COUNT: Arc> = Default::default(); + // A client server used to provide local services(audio, video, clipboard, etc.) + // for all initiative connections. + // + // [Note] + // Now we use this [`CLIENT_SERVER`] to do following operations: + // - record local audio, and send to remote + pub static ref CLIENT_SERVER: ServerPtr = new(); } pub struct Server { @@ -316,6 +330,13 @@ impl Server { } } } + + // get a new unique id + pub fn get_new_id(&mut self) -> i32 { + let new_id = self.id_count; + self.id_count += 1; + new_id + } } impl Drop for Server { diff --git a/src/server/connection.rs b/src/server/connection.rs index e4b667d54..d340021ad 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -108,6 +108,12 @@ pub struct Connection { from_switch: bool, } +impl ConnInner { + pub fn new(id: i32, tx: Option, tx_video: Option) -> Self { + Self { id, tx, tx_video } + } +} + impl Subscriber for ConnInner { #[inline] fn id(&self) -> i32 { From 1f40963b5d23fd4cc6c7be75aa55077b977ed5f0 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Fri, 4 Nov 2022 12:02:17 +0800 Subject: [PATCH 045/117] wip: connection --- src/client.rs | 16 ++++++++++--- src/client/io_loop.rs | 51 ++++++++++++++++++++++++++++------------ src/flutter_ffi.rs | 3 +++ src/server/connection.rs | 8 +++++++ 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/client.rs b/src/client.rs index e0ac68c5d..08a8de747 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1543,7 +1543,6 @@ where F: 'static + FnMut(&[u8]) + Send, { let (video_sender, video_receiver) = mpsc::channel::(); - let (audio_sender, audio_receiver) = mpsc::channel::(); let mut video_callback = video_callback; let latency_controller = LatencyController::new(); @@ -1573,8 +1572,19 @@ where } log::info!("Video decoder loop exits"); }); + let audio_sender = start_audio_thread(Some(latency_controller_cl)); + return (video_sender, audio_sender); +} + +/// Start an audio thread +/// Return a audio [`MediaSender`] +pub fn start_audio_thread( + latency_controller: Option>>, +) -> MediaSender { + let latency_controller = latency_controller.unwrap_or(LatencyController::new()); + let (audio_sender, audio_receiver) = mpsc::channel::(); std::thread::spawn(move || { - let mut audio_handler = AudioHandler::new(latency_controller_cl); + let mut audio_handler = AudioHandler::new(latency_controller); loop { if let Ok(data) = audio_receiver.recv() { match data { @@ -1592,7 +1602,7 @@ where } log::info!("Audio decoder loop exits"); }); - return (video_sender, audio_sender); + audio_sender } /// Handle latency test. diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index bcbea994b..857f94891 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -32,6 +32,7 @@ use hbb_common::tokio::{ }; use hbb_common::{allow_err, message_proto::*, sleep}; use hbb_common::{fs, log, Stream}; +use std::borrow::Borrow; use std::collections::HashMap; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -89,6 +90,7 @@ impl Remote { pub async fn io_loop(&mut self, key: &str, token: &str) { let stop_clipboard = self.start_clipboard(); + let stop_client_audio = self.start_client_audio(); let mut last_recv_time = Instant::now(); let mut received = false; let conn_type = if self.handler.is_file_transfer() { @@ -96,6 +98,7 @@ impl Remote { } else { ConnType::default() }; + match Client::start( &self.handler.id, key, @@ -224,6 +227,9 @@ impl Remote { if let Some(stop) = stop_clipboard { stop.send(()).ok(); } + if let Some(stop) = stop_client_audio { + stop.send(()).ok(); + } SERVER_KEYBOARD_ENABLED.store(false, Ordering::SeqCst); SERVER_CLIPBOARD_ENABLED.store(false, Ordering::SeqCst); SERVER_FILE_TRANSFER_ENABLED.store(false, Ordering::SeqCst); @@ -257,10 +263,7 @@ impl Remote { } // Start a local audio recorder, records audio and send to remote - fn start_client_audio( - &mut self, - audio_sender: MediaSender, - ) -> Option> { + fn start_client_audio(&mut self) -> Option> { if self.handler.is_file_transfer() || self.handler.is_port_forward() { return None; } @@ -268,29 +271,47 @@ impl Remote { let (tx, rx) = std::sync::mpsc::channel(); let (tx_audio_data, mut rx_audio_data) = hbb_common::tokio::sync::mpsc::unbounded_channel(); // Create a stand-alone inner, add subscribe to audio service - let client_conn_inner = ConnInner::new( - CLIENT_SERVER.write().unwrap().get_new_id(), - Some(tx_audio_data), - None, + let conn_id = CLIENT_SERVER.write().unwrap().get_new_id(); + let client_conn_inner = ConnInner::new(conn_id.clone(), Some(tx_audio_data), None); + // now we subscribe + CLIENT_SERVER.write().unwrap().subscribe( + audio_service::NAME, + client_conn_inner.clone(), + true, ); - CLIENT_SERVER - .write() - .unwrap() - .subscribe(audio_service::NAME, client_conn_inner, true); + let tx_audio = self.sender.clone(); std::thread::spawn(move || { loop { // check if client is closed match rx.try_recv() { Ok(_) | Err(std::sync::mpsc::TryRecvError::Disconnected) => { log::debug!("Exit local audio service of client"); + // unsubscribe + CLIENT_SERVER.write().unwrap().subscribe( + audio_service::NAME, + client_conn_inner, + false, + ); break; } _ => {} } match rx_audio_data.try_recv() { - Ok((instant, msg)) => match msg.union { - Some(_) => todo!(), - None => todo!(), + Ok((instant, msg)) => match &msg.union { + Some(message::Union::AudioFrame(frame)) => { + let mut msg = Message::new(); + msg.set_audio_frame(frame.clone()); + tx_audio.send(Data::Message(msg)).ok(); + log::debug!("send audio frame {}", frame.timestamp); + } + Some(message::Union::Misc(misc)) => { + let mut msg = Message::new(); + msg.set_misc(misc.clone()); + tx_audio.send(Data::Message(msg)).ok(); + log::debug!("send audio misc {:?}", misc.audio_format()); + } + _ => {} + None => {} }, Err(err) => { if err == TryRecvError::Empty { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index ca9314c43..4b671ff1b 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1244,6 +1244,9 @@ pub fn main_current_is_wayland() -> SyncReturn { pub fn main_is_login_wayland() -> SyncReturn { SyncReturn(is_login_wayland()) +pub fn main_start_pa() { + #[cfg(target_os = "linux")] + std::thread::spawn(crate::ipc::start_pa); } pub fn main_hide_docker() -> SyncReturn { diff --git a/src/server/connection.rs b/src/server/connection.rs index d340021ad..34adeb59b 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1533,6 +1533,10 @@ impl Connection { } _ => {} }, + Some(misc::Union::AudioFormat(format)) => { + // TODO: implement audio format handler + println!("recv audio format"); + } #[cfg(feature = "flutter")] Some(misc::Union::SwitchSidesRequest(s)) => { if let Ok(uuid) = uuid::Uuid::from_slice(&s.uuid.to_vec()[..]) { @@ -1550,6 +1554,10 @@ impl Connection { } _ => {} }, + Some(message::Union::AudioFrame(audio_frame)) => { + // TODO: implement audio frame handler + println!("recv audio frame"); + } _ => {} } } From 65ab43aa4a9ebc7563a1eceb822c57f309d24eb3 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sat, 17 Dec 2022 10:39:07 +0800 Subject: [PATCH 046/117] opt: compile --- src/flutter_ffi.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 4b671ff1b..d9f67e566 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1244,6 +1244,8 @@ pub fn main_current_is_wayland() -> SyncReturn { pub fn main_is_login_wayland() -> SyncReturn { SyncReturn(is_login_wayland()) +} + pub fn main_start_pa() { #[cfg(target_os = "linux")] std::thread::spawn(crate::ipc::start_pa); From 8e2d6945d0e0b3fd16de4d7b8883867c3236a4c8 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sun, 29 Jan 2023 11:55:37 +0800 Subject: [PATCH 047/117] feat: add audio thread in server being controlled --- src/client/io_loop.rs | 3 +-- src/server/connection.rs | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 857f94891..bac1e5d23 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -297,7 +297,7 @@ impl Remote { _ => {} } match rx_audio_data.try_recv() { - Ok((instant, msg)) => match &msg.union { + Ok((_instant, msg)) => match &msg.union { Some(message::Union::AudioFrame(frame)) => { let mut msg = Message::new(); msg.set_audio_frame(frame.clone()); @@ -311,7 +311,6 @@ impl Remote { log::debug!("send audio misc {:?}", misc.audio_format()); } _ => {} - None => {} }, Err(err) => { if err == TryRecvError::Empty { diff --git a/src/server/connection.rs b/src/server/connection.rs index 34adeb59b..2ee3bc8ed 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -5,7 +5,7 @@ use crate::clipboard_file::*; use crate::common::update_clipboard; #[cfg(windows)] use crate::portable_service::client as portable_client; -use crate::video_service; +use crate::{video_service, client::{MediaSender, start_audio_thread, LatencyController, MediaData}}; #[cfg(any(target_os = "android", target_os = "ios"))] use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel}; use crate::{ipc, VERSION}; @@ -95,6 +95,7 @@ pub struct Connection { disable_clipboard: bool, // by peer disable_audio: bool, // by peer enable_file_transfer: bool, // by peer + audio_sender: MediaSender, // audio by the remote peer/client tx_input: std_mpsc::Sender, // handle input messages video_ack_required: bool, peer_info: (String, String), @@ -168,6 +169,9 @@ impl Connection { let mut hbbs_rx = crate::hbbs_http::sync::signal_receiver(); let tx_cloned = tx.clone(); + // Start a audio thread to play the audio sent by peer. + let latency_controller = LatencyController::new(); + let audio_sender = start_audio_thread(Some(latency_controller)); let mut conn = Self { inner: ConnInner { id, @@ -209,6 +213,7 @@ impl Connection { #[cfg(windows)] portable: Default::default(), from_switch: false, + audio_sender, }; #[cfg(not(any(target_os = "android", target_os = "ios")))] tokio::spawn(async move { @@ -1534,8 +1539,9 @@ impl Connection { _ => {} }, Some(misc::Union::AudioFormat(format)) => { - // TODO: implement audio format handler - println!("recv audio format"); + if !self.disable_audio { + allow_err!(self.audio_sender.send(MediaData::AudioFormat(format))); + } } #[cfg(feature = "flutter")] Some(misc::Union::SwitchSidesRequest(s)) => { @@ -1554,9 +1560,10 @@ impl Connection { } _ => {} }, - Some(message::Union::AudioFrame(audio_frame)) => { - // TODO: implement audio frame handler - println!("recv audio frame"); + Some(message::Union::AudioFrame(frame)) => { + if !self.disable_audio { + allow_err!(self.audio_sender.send(MediaData::AudioFrame(frame))); + } } _ => {} } From 45a6fc361883a6fd1ff76a4fe3a7ca9bd54b09da Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sun, 29 Jan 2023 14:10:06 +0800 Subject: [PATCH 048/117] opt: remove latency detector on single audio --- src/client.rs | 5 +++++ src/client/helper.rs | 11 +++++++++++ src/server/connection.rs | 4 +++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index 08a8de747..b2cd0f2f7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -714,6 +714,7 @@ impl AudioHandler { .check_audio(frame.timestamp) .not() { + log::debug!("audio frame {} is ignored", frame.timestamp); return; } } @@ -724,6 +725,7 @@ impl AudioHandler { } #[cfg(target_os = "linux")] if self.simple.is_none() { + log::debug!("PulseAudio simple binding does not exists"); return; } #[cfg(target_os = "android")] @@ -768,6 +770,7 @@ impl AudioHandler { unsafe { std::slice::from_raw_parts::(buffer.as_ptr() as _, n * 4) }; self.simple.as_mut().map(|x| x.write(data_u8)); } + log::debug!("write Audio frame {} to system.", frame.timestamp); } }); } @@ -1589,9 +1592,11 @@ pub fn start_audio_thread( if let Ok(data) = audio_receiver.recv() { match data { MediaData::AudioFrame(af) => { + log::debug!("recved audio frame={}", af.timestamp); audio_handler.handle_frame(af); } MediaData::AudioFormat(f) => { + log::debug!("recved audio format, sample rate={}", f.sample_rate); audio_handler.handle_format(f); } _ => {} diff --git a/src/client/helper.rs b/src/client/helper.rs index e4736c0e8..005b2df72 100644 --- a/src/client/helper.rs +++ b/src/client/helper.rs @@ -18,6 +18,7 @@ pub struct LatencyController { last_video_remote_ts: i64, // generated on remote device update_time: Instant, allow_audio: bool, + enabled: bool } impl Default for LatencyController { @@ -26,6 +27,7 @@ impl Default for LatencyController { last_video_remote_ts: Default::default(), update_time: Instant::now(), allow_audio: Default::default(), + enabled: true } } } @@ -36,6 +38,11 @@ impl LatencyController { Arc::new(Mutex::new(LatencyController::default())) } + /// Set whether this [LatencyController] should be enabled. + pub fn set_enabled(&mut self, enable: bool) { + self.enabled = enable; + } + /// Update the latency controller with the latest video timestamp. pub fn update_video(&mut self, timestamp: i64) { self.last_video_remote_ts = timestamp; @@ -44,6 +51,10 @@ impl LatencyController { /// Check if the audio should be played based on the current latency. pub fn check_audio(&mut self, timestamp: i64) -> bool { + if !self.enabled { + self.allow_audio = true; + return self.allow_audio; + } // Compute audio latency. let expected = self.update_time.elapsed().as_millis() as i64 + self.last_video_remote_ts; let latency = expected - timestamp; diff --git a/src/server/connection.rs b/src/server/connection.rs index 2ee3bc8ed..1924cfca0 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -171,6 +171,8 @@ impl Connection { let tx_cloned = tx.clone(); // Start a audio thread to play the audio sent by peer. let latency_controller = LatencyController::new(); + // No video frame will be sent here, so we need to disable latency controller, or audio check may fail. + latency_controller.lock().unwrap().set_enabled(false); let audio_sender = start_audio_thread(Some(latency_controller)); let mut conn = Self { inner: ConnInner { @@ -1561,7 +1563,7 @@ impl Connection { _ => {} }, Some(message::Union::AudioFrame(frame)) => { - if !self.disable_audio { + if !self.disable_audio { allow_err!(self.audio_sender.send(MediaData::AudioFrame(frame))); } } From e7e8e1a18b6e3bcdf190931869527489704296a4 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sun, 29 Jan 2023 22:23:18 +0800 Subject: [PATCH 049/117] opt: send audio frame when connected --- src/client/io_loop.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index bac1e5d23..f16c9af72 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -90,7 +90,6 @@ impl Remote { pub async fn io_loop(&mut self, key: &str, token: &str) { let stop_clipboard = self.start_clipboard(); - let stop_client_audio = self.start_client_audio(); let mut last_recv_time = Instant::now(); let mut received = false; let conn_type = if self.handler.is_file_transfer() { @@ -114,6 +113,8 @@ impl Remote { SERVER_FILE_TRANSFER_ENABLED.store(true, Ordering::SeqCst); self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready self.handler.set_connection_info(direct, false); + // Start client audio when connection is established. + let stop_client_audio = self.start_client_audio(); // just build for now #[cfg(not(windows))] @@ -218,6 +219,10 @@ impl Remote { } } log::debug!("Exit io_loop of id={}", self.handler.id); + // Stop client audio server. + if let Some(stop) = stop_client_audio { + stop.send(()).ok(); + } } Err(err) => { self.handler @@ -227,9 +232,6 @@ impl Remote { if let Some(stop) = stop_clipboard { stop.send(()).ok(); } - if let Some(stop) = stop_client_audio { - stop.send(()).ok(); - } SERVER_KEYBOARD_ENABLED.store(false, Ordering::SeqCst); SERVER_CLIPBOARD_ENABLED.store(false, Ordering::SeqCst); SERVER_FILE_TRANSFER_ENABLED.store(false, Ordering::SeqCst); From 4f3c5b42ae158a52a1d276964b38034241e0b187 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 30 Jan 2023 01:39:42 +0800 Subject: [PATCH 050/117] opt: send audio format and data after login successfully. --- src/client/io_loop.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index f16c9af72..d568feb4e 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -44,6 +44,8 @@ pub struct Remote { audio_sender: MediaSender, receiver: mpsc::UnboundedReceiver, sender: mpsc::UnboundedSender, + // Stop sending local audio to remote client. + stop_local_audio_sender: Option>, old_clipboard: Arc>, read_jobs: Vec, write_jobs: Vec, @@ -85,6 +87,7 @@ impl Remote { data_count: Arc::new(AtomicUsize::new(0)), frame_count, video_format: CodecFormat::Unknown, + stop_local_audio_sender: None, } } @@ -113,8 +116,6 @@ impl Remote { SERVER_FILE_TRANSFER_ENABLED.store(true, Ordering::SeqCst); self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready self.handler.set_connection_info(direct, false); - // Start client audio when connection is established. - let stop_client_audio = self.start_client_audio(); // just build for now #[cfg(not(windows))] @@ -220,8 +221,8 @@ impl Remote { } log::debug!("Exit io_loop of id={}", self.handler.id); // Stop client audio server. - if let Some(stop) = stop_client_audio { - stop.send(()).ok(); + if let Some(s) = self.stop_local_audio_sender.take() { + s.send(()).ok(); } } Err(err) => { @@ -865,6 +866,15 @@ impl Remote { }); } } + // Start audio thread for playback + if !self.handler.is_file_transfer() && !self.handler.is_port_forward() { + // Cancel previous local audio session. + if let Some(sender) = self.stop_local_audio_sender.take() { + allow_err!(sender.send(())); + } + // Start client audio when connection is established. + self.stop_local_audio_sender = self.start_client_audio(); + } if self.handler.is_file_transfer() { self.handler.load_last_jobs(); From 3b34e2ea453fe6a7667e980fcd05b5d009cc065c Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 30 Jan 2023 11:15:47 +0800 Subject: [PATCH 051/117] feat: run local audio server at start --- flutter/lib/models/native_model.dart | 8 ++++++-- src/ui.rs | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index 628bf502d..34a673953 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -118,8 +118,12 @@ class PlatformFFI { // Start a dbus service, no need to await _ffiBind.mainStartDbusServer(); } else if (Platform.isMacOS && isMain) { - // Start an ipc server for handling url schemes. - _ffiBind.mainStartIpcUrlServer(); + Future.wait([ + // Start dbus service. + _ffiBind.mainStartDbusServer(), + // Start local audio pulseaudio server. + _ffiBind.mainStartPa() + ]); } _startListenEvent(_ffiBind); // global event try { diff --git a/src/ui.rs b/src/ui.rs index 8763194fe..7973a0ba4 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -95,6 +95,9 @@ pub fn start(args: &mut [String]) { frame.event_handler(UI {}); frame.sciter_handler(UIHostHandler {}); page = "index.html"; + // Start pulse audio local server. + #[cfg(target_os = "linux")] + std::thread::spawn(crate::ipc::start_pa); } else if args[0] == "--install" { frame.event_handler(UI {}); frame.sciter_handler(UIHostHandler {}); From 9134c2826e0205aaff80cffe299c0f5c6a71ecc0 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 30 Jan 2023 11:32:46 +0800 Subject: [PATCH 052/117] feat: set audio only mode --- src/client/helper.rs | 19 ++++++++++--------- src/server/connection.rs | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/client/helper.rs b/src/client/helper.rs index 005b2df72..248cf5928 100644 --- a/src/client/helper.rs +++ b/src/client/helper.rs @@ -18,7 +18,7 @@ pub struct LatencyController { last_video_remote_ts: i64, // generated on remote device update_time: Instant, allow_audio: bool, - enabled: bool + audio_only: bool } impl Default for LatencyController { @@ -27,7 +27,7 @@ impl Default for LatencyController { last_video_remote_ts: Default::default(), update_time: Instant::now(), allow_audio: Default::default(), - enabled: true + audio_only: true } } } @@ -38,9 +38,9 @@ impl LatencyController { Arc::new(Mutex::new(LatencyController::default())) } - /// Set whether this [LatencyController] should be enabled. - pub fn set_enabled(&mut self, enable: bool) { - self.enabled = enable; + /// Set whether this [LatencyController] should be working in audio only mode. + pub fn set_audio_only(&mut self, only: bool) { + self.audio_only = only; } /// Update the latency controller with the latest video timestamp. @@ -51,10 +51,6 @@ impl LatencyController { /// Check if the audio should be played based on the current latency. pub fn check_audio(&mut self, timestamp: i64) -> bool { - if !self.enabled { - self.allow_audio = true; - return self.allow_audio; - } // Compute audio latency. let expected = self.update_time.elapsed().as_millis() as i64 + self.last_video_remote_ts; let latency = expected - timestamp; @@ -70,6 +66,11 @@ impl LatencyController { self.allow_audio = true; } } + // No video frame here, which means the update time is not triggered. + // We manually update the time here. + if self.audio_only { + self.update_time = Instant::now(); + } self.allow_audio } } diff --git a/src/server/connection.rs b/src/server/connection.rs index 1924cfca0..d5c2103b1 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -172,7 +172,7 @@ impl Connection { // Start a audio thread to play the audio sent by peer. let latency_controller = LatencyController::new(); // No video frame will be sent here, so we need to disable latency controller, or audio check may fail. - latency_controller.lock().unwrap().set_enabled(false); + latency_controller.lock().unwrap().set_audio_only(true); let audio_sender = start_audio_thread(Some(latency_controller)); let mut conn = Self { inner: ConnInner { From 95d06e160b21df29a62926fefd46c9f318c2cae1 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 30 Jan 2023 11:51:03 +0800 Subject: [PATCH 053/117] fix: latency --- src/client/helper.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/client/helper.rs b/src/client/helper.rs index 248cf5928..e3acf3a44 100644 --- a/src/client/helper.rs +++ b/src/client/helper.rs @@ -27,7 +27,7 @@ impl Default for LatencyController { last_video_remote_ts: Default::default(), update_time: Instant::now(), allow_audio: Default::default(), - audio_only: true + audio_only: false } } } @@ -53,7 +53,11 @@ impl LatencyController { pub fn check_audio(&mut self, timestamp: i64) -> bool { // Compute audio latency. let expected = self.update_time.elapsed().as_millis() as i64 + self.last_video_remote_ts; - let latency = expected - timestamp; + let latency = if self.audio_only { + expected + } else { + expected - timestamp + }; // Set MAX and MIN, avoid fixing too frequently. if self.allow_audio { if latency.abs() > MAX_LATENCY { @@ -66,11 +70,9 @@ impl LatencyController { self.allow_audio = true; } } - // No video frame here, which means the update time is not triggered. + // No video frame here, which means the update time is not up to date. // We manually update the time here. - if self.audio_only { - self.update_time = Instant::now(); - } + self.update_time = Instant::now(); self.allow_audio } } From cb228bef2b7093115909686a31d304d47eaa6e1e Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 30 Jan 2023 20:30:35 +0800 Subject: [PATCH 054/117] feat: add audio switch ui --- flutter/lib/consts.dart | 6 +++ .../lib/desktop/widgets/remote_menubar.dart | 26 +++++++++++++ libs/hbb_common/protos/message.proto | 6 +++ libs/hbb_common/src/config.rs | 10 +++++ src/client.rs | 39 +++++++++++++++++++ src/flutter_ffi.rs | 14 +++++++ src/lang/ca.rs | 3 ++ src/lang/cn.rs | 3 ++ src/lang/cs.rs | 3 ++ src/lang/da.rs | 3 ++ src/lang/de.rs | 3 ++ src/lang/eo.rs | 3 ++ src/lang/es.rs | 4 ++ src/lang/fa.rs | 3 ++ src/lang/fr.rs | 3 ++ src/lang/gr.rs | 3 ++ src/lang/hu.rs | 3 ++ src/lang/id.rs | 3 ++ src/lang/it.rs | 3 ++ src/lang/ja.rs | 3 ++ src/lang/ko.rs | 3 ++ src/lang/kz.rs | 3 ++ src/lang/pl.rs | 3 ++ src/lang/pt_PT.rs | 3 ++ src/lang/ptbr.rs | 3 ++ src/lang/ro.rs | 3 ++ src/lang/ru.rs | 5 +++ src/lang/sk.rs | 3 ++ src/lang/sl.rs | 3 ++ src/lang/sq.rs | 3 ++ src/lang/sr.rs | 3 ++ src/lang/sv.rs | 3 ++ src/lang/template.rs | 3 ++ src/lang/th.rs | 3 ++ src/lang/tr.rs | 3 ++ src/lang/tw.rs | 3 ++ src/lang/ua.rs | 3 ++ src/lang/vn.rs | 3 ++ src/ui_session_interface.rs | 19 +++++++++ 39 files changed, 219 insertions(+) diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index c95c62fcc..99130f892 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -106,6 +106,12 @@ const kRemoteImageQualityLow = 'low'; /// [kRemoteImageQualityCustom] Custom image quality. const kRemoteImageQualityCustom = 'custom'; +/// [kRemoteAudioGuestToHost] Guest to host audio mode(default). +const kRemoteAudioGuestToHost = 'guest-to-host'; + +/// [kRemoteAudioTwoWay] two-way audio mode(default). +const kRemoteAudioTwoWay = 'two-way'; + const kIgnoreDpi = true; /// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 36b9504c0..1e5723b61 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1106,6 +1106,30 @@ class _RemoteMenubarState extends State { padding: padding, ), MenuEntryDivider(), + MenuEntryRadios( + text: translate('Audio Transmission Mode'), + optionsGetter: () => [ + MenuEntryRadioOption( + text: translate('Guest to Host'), + value: kRemoteAudioGuestToHost, + dismissOnClicked: true, + ), + MenuEntryRadioOption( + text: translate('Two way'), + value: kRemoteAudioTwoWay, + dismissOnClicked: true, + ), + ], + curOptionGetter: () async => + // null means peer id is not found, which there's no need to care about + await bind.sessionGetAudioMode(id: widget.id) ?? '', + optionSetter: (String oldValue, String newValue) async { + if (oldValue != newValue) { + await bind.sessionSetAudioMode(id: widget.id, value: newValue); + } + }, + padding: padding, + ), ]; if (widget.state.viewStyle.value == kRemoteViewStyleOriginal) { @@ -1337,6 +1361,8 @@ class _RemoteMenubarState extends State { if (perms['audio'] != false) { displayMenu .add(_createSwitchMenuEntry('Mute', 'disable-audio', padding, true)); + displayMenu + .add(_createSwitchMenuEntry('Mute', 'disable-audio', padding, true)); } if (Platform.isWindows && diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index b7965f237..da4865069 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -444,6 +444,11 @@ enum ImageQuality { Best = 4; } +enum AudioMode { + GuestToHost = 0; + TwoWay = 1; +} + message VideoCodecState { enum PreferCodec { Auto = 0; @@ -475,6 +480,7 @@ message OptionMessage { BoolOption enable_file_transfer = 9; VideoCodecState video_codec_state = 10; int32 custom_fps = 11; + AudioMode audio_mode = 12; } message TestDelay { diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 71dd9a5c6..6032ae9c7 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -212,6 +212,11 @@ pub struct PeerConfig { deserialize_with = "PeerConfig::deserialize_image_quality" )] pub image_quality: String, + #[serde( + default = "PeerConfig::default_audio_mode", + deserialize_with = "PeerConfig::deserialize_audio_mode" + )] + pub audio_mode: String, #[serde( default = "PeerConfig::default_custom_image_quality", deserialize_with = "PeerConfig::deserialize_custom_image_quality" @@ -996,6 +1001,11 @@ impl PeerConfig { deserialize_image_quality, UserDefaultConfig::load().get("image_quality") ); + serde_field_string!( + default_audio_mode, + deserialize_audio_mode, + "guest-to-host".to_owned() + ); fn default_custom_image_quality() -> Vec { let f: f64 = UserDefaultConfig::load() diff --git a/src/client.rs b/src/client.rs index b2cd0f2f7..54796a935 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1252,6 +1252,27 @@ impl LoginConfigHandler { } } + /// Parse the audio mode option. + /// Return [`AudioMode`] if the option is valid, otherwise return `None`. + /// + /// # Arguments + /// + /// * `q` - The audio mode option. + /// * `ignore_default` - Ignore the default value. + fn get_audio_mode_enum(&self, q: &str, ignore_default: bool) -> Option { + if q == "guest-to-host" { + Some(AudioMode::GuestToHost) + } else if q == "two-way" { + Some(AudioMode::TwoWay) + } else { + if ignore_default { + None + } else { + Some(AudioMode::GuestToHost) + } + } + } + /// Get the status of a toggle option. /// /// # Arguments @@ -1338,6 +1359,24 @@ impl LoginConfigHandler { res } + pub fn save_audio_mode(&mut self, value: String) -> Option { + let mut res = None; + if let Some(q) = self.get_audio_mode_enum(&value, false) { + let mut misc = Misc::new(); + misc.set_option(OptionMessage { + audio_mode: q.into(), + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + res = Some(msg_out); + } + let mut config = self.load_config(); + config.audio_mode = value; + self.save_config(config); + res + } + /// Create a [`Message`] for saving custom fps. /// /// # Arguments diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index d9f67e566..10fd67fdd 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -233,6 +233,20 @@ pub fn session_set_image_quality(id: String, value: String) { } } +pub fn session_get_audio_mode(id: String) -> Option { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + Some(session.get_audio_mode()) + } else { + None + } +} + +pub fn session_set_audio_mode(id: String, value: String) { + if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { + session.save_audio_mode(value); + } +} + pub fn session_get_keyboard_mode(id: String) -> Option { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_keyboard_mode()) diff --git a/src/lang/ca.rs b/src/lang/ca.rs index f2210f971..197435158 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 00d62946f..c74f352ce 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "帧率"), ("Auto", "自动"), ("Other Default Options", "其它默认选项"), + ("Guest to Host", "被控到主机"), + ("Two way", "双向"), + ("Audio Transmission Mode", "音频传输模式"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 453ecefb3..d956ddf53 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index dcaeb3eaa..9e771567a 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -436,6 +436,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Switch Sides", ""), ("Please confirm if you want to share your desktop?", ""), ("Closed as expected", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ("Display", ""), ("Default View Style", ""), ("Default Scroll Style", ""), diff --git a/src/lang/de.rs b/src/lang/de.rs index 2d6d3d069..a112385a6 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "fps"), ("Auto", "Automatisch"), ("Other Default Options", "Weitere Standardoptionen"), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 0c7f13d7e..342eac51c 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 5fdb7ee2c..74acd8c69 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -445,5 +445,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", "Otras opciones predeterminadas"), + ("Closed as expected", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index dd1c75bac..50e883227 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "خودکار"), ("Other Default Options", "سایر گزینه های پیش فرض"), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 3b7f23ab9..9bfdb6b1e 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "Auto"), ("Other Default Options", "Autres options par défaut"), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/gr.rs b/src/lang/gr.rs index bc25ab6c6..a569b750f 100644 --- a/src/lang/gr.rs +++ b/src/lang/gr.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 49ce8f140..e28294de0 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 0fa6e0293..ece6c9233 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index d84b56a8a..e252219c1 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "Auto"), ("Other Default Options", "Altre Opzioni Predefinite"), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 35e20d7fd..036bc8ec7 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index d03b07992..6da983849 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 2006c67d1..459139f58 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index b7ccbdbb1..483879d49 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "Auto"), ("Other Default Options", "Inne opcje domyślne"), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 64e5e9315..cff00333a 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 0f64ae67f..9fe5eab8c 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 7e209dff8..36e2a99de 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 54b064c18..31f24a5e8 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -445,5 +445,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "Авто"), ("Other Default Options", "Другие параметры по умолчанию"), + ("Please confirm if you want to share your desktop?", "Подтвердите, что хотите поделиться своим рабочим столом?"), + ("Closed as expected", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index a703c0799..8cf858df0 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 16c948ceb..0e2208c3c 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 285a51732..44159fb4a 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index dd943e0e6..892b3664e 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 3050ff635..619a68508 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 7572da9de..f0458b115 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 535e4e772..f61ba325a 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 80b384c6c..cade148a7 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index f5d9539d8..46cc90c1e 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "幀率"), ("Auto", "自動"), ("Other Default Options", "其它默認選項"), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 37a7d6bcd..7c355edd5 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index d78f5aa7b..f7640ae50 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -445,5 +445,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Guest to Host", ""), + ("Two way", ""), + ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 4fc5db743..234c9a4d7 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -89,6 +89,18 @@ impl Session { self.lc.write().unwrap().save_keyboard_mode(value); } + pub fn get_audio_mode(&self) -> String { + self.lc.read().unwrap().audio_mode.clone() + } + + pub fn save_audio_mode(&self, value: String) { + let msg = self.lc.write().unwrap().save_audio_mode(value); + // Notify remote guest that the audio mode has been changed. + if let Some(msg) = msg { + self.send(Data::Message(msg)); + } + } + pub fn save_view_style(&mut self, value: String) { self.lc.write().unwrap().save_view_style(value); } @@ -653,6 +665,13 @@ impl Session { } } } + + fn get_audio_transmission_mode(&self, id: &str) { + + } + fn set_audio_transmission_mode(&self, id: &str, mode: String) { + + } } pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { From 393e0e9afbc69df74f95ee98306fc322c5ef456f Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 30 Jan 2023 21:53:26 +0800 Subject: [PATCH 055/117] add: divider --- flutter/lib/desktop/widgets/remote_menubar.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 1e5723b61..bb2079930 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1130,6 +1130,7 @@ class _RemoteMenubarState extends State { }, padding: padding, ), + MenuEntryDivider(), ]; if (widget.state.viewStyle.value == kRemoteViewStyleOriginal) { From 8ab49d11d149de458d6ea95d1543b9c384568632 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 30 Jan 2023 22:06:52 +0800 Subject: [PATCH 056/117] feat: add audio mode config --- src/client.rs | 5 ++-- src/client/io_loop.rs | 46 +++++++++++++++++++++++++++++-------- src/ui_session_interface.rs | 4 ++++ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/client.rs b/src/client.rs index 54796a935..d76f930c8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1259,7 +1259,7 @@ impl LoginConfigHandler { /// /// * `q` - The audio mode option. /// * `ignore_default` - Ignore the default value. - fn get_audio_mode_enum(&self, q: &str, ignore_default: bool) -> Option { + pub fn get_audio_mode_enum(q: &str, ignore_default: bool) -> Option { if q == "guest-to-host" { Some(AudioMode::GuestToHost) } else if q == "two-way" { @@ -1361,7 +1361,7 @@ impl LoginConfigHandler { pub fn save_audio_mode(&mut self, value: String) -> Option { let mut res = None; - if let Some(q) = self.get_audio_mode_enum(&value, false) { + if let Some(q) = LoginConfigHandler::get_audio_mode_enum(&value, false) { let mut misc = Misc::new(); misc.set_option(OptionMessage { audio_mode: q.into(), @@ -1981,6 +1981,7 @@ pub enum Data { RemovePortForward(i32), AddPortForward((i32, String, i32)), ToggleClipboardFile, + ChangeAudioMode(AudioMode), NewRDP, SetConfirmOverrideFile((i32, i32, bool, bool, bool)), AddJob((i32, String, String, i32, bool, bool)), diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index d568feb4e..af8c1048b 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1,5 +1,5 @@ use crate::client::{ - Client, CodecFormat, MediaData, MediaSender, QualityStatus, MILLI1, SEC30, + Client, CodecFormat, LoginConfigHandler, MediaData, MediaSender, QualityStatus, MILLI1, SEC30, SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED, SERVER_KEYBOARD_ENABLED, }; #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -386,6 +386,24 @@ impl Remote { Data::ToggleClipboardFile => { self.check_clipboard_file_context(); } + Data::ChangeAudioMode(audio_mode) => { + match audio_mode { + AudioMode::GuestToHost => { + if let Some(sender) = self.stop_local_audio_sender.take() { + allow_err!(sender.send(())); + } + } + AudioMode::TwoWay => { + // Start audio thread for playback. + // Cancel previous local audio session. + if let Some(sender) = self.stop_local_audio_sender.take() { + allow_err!(sender.send(())); + } + // Start client audio when connection is established. + self.stop_local_audio_sender = self.start_client_audio(); + } + } + } Data::Message(msg) => { allow_err!(peer.send(&msg).await); } @@ -866,19 +884,27 @@ impl Remote { }); } } - // Start audio thread for playback - if !self.handler.is_file_transfer() && !self.handler.is_port_forward() { - // Cancel previous local audio session. - if let Some(sender) = self.stop_local_audio_sender.take() { - allow_err!(sender.send(())); - } - // Start client audio when connection is established. - self.stop_local_audio_sender = self.start_client_audio(); - } if self.handler.is_file_transfer() { self.handler.load_last_jobs(); } + + // Start audio thread for playback if current audio mode is two-way transmission. + if !self.handler.is_file_transfer() && !self.handler.is_port_forward() { + let audio_mode = LoginConfigHandler::get_audio_mode_enum( + self.handler.load_config().audio_mode.as_str(), + false, + ) + .unwrap_or(AudioMode::GuestToHost); + if audio_mode == AudioMode::TwoWay { + // Cancel previous local audio session. + if let Some(sender) = self.stop_local_audio_sender.take() { + allow_err!(sender.send(())); + } + // Start client audio when connection is established. + self.stop_local_audio_sender = self.start_client_audio(); + } + } } _ => {} }, diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 234c9a4d7..73414e405 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -94,6 +94,10 @@ impl Session { } pub fn save_audio_mode(&self, value: String) { + let mode = LoginConfigHandler::get_audio_mode_enum(value.as_str(), false); + if let Some(mode)= mode { + self.send(Data::ChangeAudioMode(mode)); + } let msg = self.lc.write().unwrap().save_audio_mode(value); // Notify remote guest that the audio mode has been changed. if let Some(msg) = msg { From 05822991bfaa48fbf86ff31b734d77a431a75cde Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 30 Jan 2023 22:57:20 +0800 Subject: [PATCH 057/117] opt: rename to dual-way --- flutter/lib/consts.dart | 4 ++-- flutter/lib/desktop/widgets/remote_menubar.dart | 4 ++-- libs/hbb_common/protos/message.proto | 2 +- src/client.rs | 4 ++-- src/client/io_loop.rs | 7 ++++--- src/lang/ca.rs | 2 +- src/lang/cn.rs | 2 +- src/lang/cs.rs | 2 +- src/lang/da.rs | 2 +- src/lang/de.rs | 2 +- src/lang/eo.rs | 2 +- src/lang/es.rs | 2 +- src/lang/fa.rs | 2 +- src/lang/fr.rs | 2 +- src/lang/gr.rs | 2 +- src/lang/hu.rs | 2 +- src/lang/id.rs | 2 +- src/lang/it.rs | 2 +- src/lang/ja.rs | 2 +- src/lang/ko.rs | 2 +- src/lang/kz.rs | 2 +- src/lang/pl.rs | 2 +- src/lang/pt_PT.rs | 2 +- src/lang/ptbr.rs | 2 +- src/lang/ro.rs | 2 +- src/lang/ru.rs | 2 +- src/lang/sk.rs | 2 +- src/lang/sl.rs | 2 +- src/lang/sq.rs | 2 +- src/lang/sr.rs | 2 +- src/lang/sv.rs | 2 +- src/lang/template.rs | 2 +- src/lang/th.rs | 2 +- src/lang/tr.rs | 2 +- src/lang/tw.rs | 2 +- src/lang/ua.rs | 2 +- src/lang/vn.rs | 2 +- src/ui_session_interface.rs | 7 ------- 38 files changed, 43 insertions(+), 49 deletions(-) diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 99130f892..26e25a209 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -109,8 +109,8 @@ const kRemoteImageQualityCustom = 'custom'; /// [kRemoteAudioGuestToHost] Guest to host audio mode(default). const kRemoteAudioGuestToHost = 'guest-to-host'; -/// [kRemoteAudioTwoWay] two-way audio mode(default). -const kRemoteAudioTwoWay = 'two-way'; +/// [kRemoteAudioDualWay] dual-way audio mode(default). +const kRemoteAudioDualWay = 'dual-way'; const kIgnoreDpi = true; diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index bb2079930..9864947c6 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1115,8 +1115,8 @@ class _RemoteMenubarState extends State { dismissOnClicked: true, ), MenuEntryRadioOption( - text: translate('Two way'), - value: kRemoteAudioTwoWay, + text: translate('Dual way'), + value: kRemoteAudioDualWay, dismissOnClicked: true, ), ], diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index da4865069..48b999438 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -446,7 +446,7 @@ enum ImageQuality { enum AudioMode { GuestToHost = 0; - TwoWay = 1; + DualWay = 1; } message VideoCodecState { diff --git a/src/client.rs b/src/client.rs index d76f930c8..649b180bb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1262,8 +1262,8 @@ impl LoginConfigHandler { pub fn get_audio_mode_enum(q: &str, ignore_default: bool) -> Option { if q == "guest-to-host" { Some(AudioMode::GuestToHost) - } else if q == "two-way" { - Some(AudioMode::TwoWay) + } else if q == "dual-way" { + Some(AudioMode::DualWay) } else { if ignore_default { None diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index af8c1048b..a284fdade 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -393,7 +393,7 @@ impl Remote { allow_err!(sender.send(())); } } - AudioMode::TwoWay => { + AudioMode::DualWay => { // Start audio thread for playback. // Cancel previous local audio session. if let Some(sender) = self.stop_local_audio_sender.take() { @@ -889,14 +889,15 @@ impl Remote { self.handler.load_last_jobs(); } - // Start audio thread for playback if current audio mode is two-way transmission. + // Start audio thread for playback if current audio mode is dual-way transmission. if !self.handler.is_file_transfer() && !self.handler.is_port_forward() { let audio_mode = LoginConfigHandler::get_audio_mode_enum( self.handler.load_config().audio_mode.as_str(), false, ) .unwrap_or(AudioMode::GuestToHost); - if audio_mode == AudioMode::TwoWay { + log::debug!("current audio mode: {:?}", audio_mode); + if audio_mode == AudioMode::DualWay { // Cancel previous local audio session. if let Some(sender) = self.stop_local_audio_sender.take() { allow_err!(sender.send(())); diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 197435158..e45dc5fb5 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index c74f352ce..84bfcb384 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", "自动"), ("Other Default Options", "其它默认选项"), ("Guest to Host", "被控到主机"), - ("Two way", "双向"), + ("Dual way", "双向"), ("Audio Transmission Mode", "音频传输模式"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index d956ddf53..ef9cd7bf8 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 9e771567a..32aa1f0af 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -437,7 +437,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please confirm if you want to share your desktop?", ""), ("Closed as expected", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ("Display", ""), ("Default View Style", ""), diff --git a/src/lang/de.rs b/src/lang/de.rs index a112385a6..f8fac0737 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", "Automatisch"), ("Other Default Options", "Weitere Standardoptionen"), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 342eac51c..4aa2be8db 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 74acd8c69..932936da3 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -447,7 +447,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", "Otras opciones predeterminadas"), ("Closed as expected", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 50e883227..b8c45fbee 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", "خودکار"), ("Other Default Options", "سایر گزینه های پیش فرض"), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 9bfdb6b1e..64a8b4e40 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", "Auto"), ("Other Default Options", "Autres options par défaut"), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/gr.rs b/src/lang/gr.rs index a569b750f..3918db55b 100644 --- a/src/lang/gr.rs +++ b/src/lang/gr.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index e28294de0..edad7ecd0 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index ece6c9233..1b2dc4ad5 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index e252219c1..27432303d 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", "Auto"), ("Other Default Options", "Altre Opzioni Predefinite"), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 036bc8ec7..ae375b8ee 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 6da983849..417f88fe6 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 459139f58..e852278dd 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 483879d49..4cce52e08 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", "Auto"), ("Other Default Options", "Inne opcje domyślne"), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index cff00333a..29252926f 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 9fe5eab8c..8ec40cf13 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 36e2a99de..c4f798abf 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 31f24a5e8..949eba641 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -448,7 +448,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please confirm if you want to share your desktop?", "Подтвердите, что хотите поделиться своим рабочим столом?"), ("Closed as expected", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 8cf858df0..7de4d10ce 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 0e2208c3c..bf30f96d8 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 44159fb4a..db560166a 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 892b3664e..599cd651b 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 619a68508..c0616300c 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index f0458b115..282b564d7 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index f61ba325a..b2bee959a 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index cade148a7..b6efeaf0a 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 46cc90c1e..eea71e6bf 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", "自動"), ("Other Default Options", "其它默認選項"), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 7c355edd5..f0d85a551 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index f7640ae50..5e4009570 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -446,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", ""), ("Guest to Host", ""), - ("Two way", ""), + ("Dual way", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 73414e405..1e7848505 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -669,13 +669,6 @@ impl Session { } } } - - fn get_audio_transmission_mode(&self, id: &str) { - - } - fn set_audio_transmission_mode(&self, id: &str, mode: String) { - - } } pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { From cab1fc719aed30a7f0afac289d55e1b03375ac91 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 30 Jan 2023 23:42:38 +0800 Subject: [PATCH 058/117] feat: add audio mode in sciter --- src/ui/header.tis | 10 +++++++++- src/ui/remote.rs | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ui/header.tis b/src/ui/header.tis index dd0b35541..e3f0c70a1 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -183,6 +183,9 @@ class Header: Reactor.Component {
  • {svg_checkmark}{translate('Balanced')}
  • {svg_checkmark}{translate('Optimize reaction time')}
  • {svg_checkmark}{translate('Custom')}
  • +
    +
  • {svg_checkmark}{translate('Guest to Host')}
  • +
  • {svg_checkmark}{translate('Dual way')}
  • {show_codec ?
  • {svg_checkmark}Auto
  • @@ -378,7 +381,7 @@ class Header: Reactor.Component { togglePrivacyMode(me.id); } else if (me.id == "show-quality-monitor") { toggleQualityMonitor(me.id); - }else if (me.attributes.hasClass("toggle-option")) { + } else if (me.attributes.hasClass("toggle-option")) { handler.toggle_option(me.id); toggleMenuState(); } else if (!me.attributes.hasClass("selected")) { @@ -391,6 +394,8 @@ class Header: Reactor.Component { } else if (type == "codec-preference") { handler.set_option("codec-preference", me.id); handler.change_prefer_codec(); + } else if (type == "audio-mode") { + handler.save_audio_mode(me.id); } toggleMenuState(); } @@ -434,6 +439,9 @@ function toggleMenuState() { var c = handler.get_option("codec-preference"); if (!c) c = "auto"; values.push(c); + var a = handler.get_audio_mode(); + if (!a) a = "guest-to-host"; + values.push(a); for (var el in $$(menu#display-options li)) { el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0); } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 21504d20d..541d3a141 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -420,6 +420,8 @@ impl sciter::EventHandler for SciterSession { fn supported_hwcodec(); fn change_prefer_codec(); fn restart_remote_device(); + fn save_audio_mode(String); + fn get_audio_mode(); } } From 7e5c5b50e5a6dd6b9c1f265cfb1520db4319e739 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 31 Jan 2023 10:01:31 +0800 Subject: [PATCH 059/117] feat: set to default input device when in dual-way --- src/client/io_loop.rs | 10 +++++++-- src/common.rs | 44 +++++++++++++++++++++++++++++++++++++++- src/flutter_ffi.rs | 10 +++++++++ src/platform/linux.rs | 18 ++++++++++++++++ src/server/connection.rs | 7 ++++++- 5 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index a284fdade..9117c8c5f 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -2,13 +2,14 @@ use crate::client::{ Client, CodecFormat, LoginConfigHandler, MediaData, MediaSender, QualityStatus, MILLI1, SEC30, SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED, SERVER_KEYBOARD_ENABLED, }; +use crate::common::{get_default_sound_input, set_sound_input}; #[cfg(not(any(target_os = "android", target_os = "ios")))] use crate::common::{check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL}; use crate::{audio_service, common, ConnInner, CLIENT_SERVER}; #[cfg(windows)] use clipboard::{cliprdr::CliprdrClientContext, ContextSend}; -use hbb_common::futures::channel::mpsc::unbounded; + use hbb_common::tokio::sync::mpsc::error::TryRecvError; use crate::server::Service; @@ -32,7 +33,7 @@ use hbb_common::tokio::{ }; use hbb_common::{allow_err, message_proto::*, sleep}; use hbb_common::{fs, log, Stream}; -use std::borrow::Borrow; + use std::collections::HashMap; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -270,6 +271,11 @@ impl Remote { if self.handler.is_file_transfer() || self.handler.is_port_forward() { return None; } + // Switch to default input device + let default_sound_device = get_default_sound_input(); + if let Some(device) = default_sound_device { + set_sound_input(device); + } // Create a channel to receive error or closed message let (tx, rx) = std::sync::mpsc::channel(); let (tx_audio_data, mut rx_audio_data) = hbb_common::tokio::sync::mpsc::unbounded_channel(); diff --git a/src/common.rs b/src/common.rs index c2d5a81f0..9cbc9b150 100644 --- a/src/common.rs +++ b/src/common.rs @@ -30,6 +30,8 @@ use hbb_common::{ // #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))] use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all}; +use crate::ui_interface::{set_option, get_option}; + pub type NotifyMessageBox = fn(String, String, String, String) -> dyn Future; pub const CLIPBOARD_NAME: &'static str = "clipboard"; @@ -105,6 +107,46 @@ pub fn check_clipboard( None } +/// Set sound input device. +pub fn set_sound_input(device: String) { + let prior_device = get_option("audio-input".to_owned()); + if prior_device != device { + log::info!("switch to audio input device {}", device); + set_option("audio-input".to_owned(), device); + } else { + log::info!("audio input is already set to {}", device); + } +} + +/// Get system's default sound input device name. +#[inline] +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub fn get_default_sound_input() -> Option { + #[cfg(not(target_os = "linux"))] + { + use cpal::traits::{DeviceTrait, HostTrait}; + let host = cpal::default_host(); + let dev = host.default_input_device(); + return if let Some(dev) = dev { + match dev.name() { + Ok(name) => Some(name), + Err(_) => None, + } + } else { + None + }; + } + #[cfg(target_os = "linux")] + { + let input = crate::platform::linux::get_default_pa_source(); + return if let Some(input) = input { + Some(input.1) + } else { + None + }; + } +} + #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn update_clipboard(clipboard: Clipboard, old: Option<&Arc>>) { let content = if clipboard.compress { @@ -715,5 +757,5 @@ pub fn make_fd_to_json(id: i32, path: String, entries: &Vec) -> Strin #[cfg(test)] mod test_common { - use super::*; + } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 10fd67fdd..31cb07c07 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -4,6 +4,9 @@ use std::str::FromStr; use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer}; use serde_json::json; +use crate::common::{is_keyboard_mode_supported, get_default_sound_input}; +use hbb_common::message_proto::KeyboardMode; +use hbb_common::ResultType; use hbb_common::{ config::{self, LocalConfig, ONLINE, PeerConfig}, fs, log, @@ -534,6 +537,13 @@ pub fn main_get_sound_inputs() -> Vec { vec![String::from("")] } +pub fn main_get_default_sound_input() -> Option { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + return get_default_sound_input(); + #[cfg(any(target_os = "android", target_os = "ios"))] + String::from("") +} + pub fn main_get_hostname() -> SyncReturn { SyncReturn(crate::common::hostname()) } diff --git a/src/platform/linux.rs b/src/platform/linux.rs index ac3b32a49..8fa95ac90 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -534,6 +534,24 @@ pub fn get_pa_sources() -> Vec<(String, String)> { out } +pub fn get_default_pa_source() -> Option<(String, String)> { + use pulsectl::controllers::*; + match SourceController::create() { + Ok(mut handler) => { + if let Ok(dev) = handler.get_default_device() { + return Some(( + dev.name.unwrap_or("".to_owned()), + dev.description.unwrap_or("".to_owned()), + )); + } + } + Err(err) => { + log::error!("Failed to get_pa_source: {:?}", err); + } + } + None +} + pub fn lock_screen() { std::process::Command::new("xdg-screensaver") .arg("lock") diff --git a/src/server/connection.rs b/src/server/connection.rs index d5c2103b1..20cbe0f86 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -5,7 +5,7 @@ use crate::clipboard_file::*; use crate::common::update_clipboard; #[cfg(windows)] use crate::portable_service::client as portable_client; -use crate::{video_service, client::{MediaSender, start_audio_thread, LatencyController, MediaData}}; +use crate::{video_service, client::{MediaSender, start_audio_thread, LatencyController, MediaData}, common::{get_default_sound_input, set_sound_input}}; #[cfg(any(target_os = "android", target_os = "ios"))] use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel}; use crate::{ipc, VERSION}; @@ -1542,6 +1542,11 @@ impl Connection { }, Some(misc::Union::AudioFormat(format)) => { if !self.disable_audio { + // Switch to default input device + let default_sound_device = get_default_sound_input(); + if let Some(device) = default_sound_device { + set_sound_input(device); + } allow_err!(self.audio_sender.send(MediaData::AudioFormat(format))); } } From 60925057f0c66ded4d9dc66b52d85b86059ffc1c Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 31 Jan 2023 10:23:58 +0800 Subject: [PATCH 060/117] fix: poison error on setting sound input --- src/common.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common.rs b/src/common.rs index 9cbc9b150..70d50619e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -112,7 +112,9 @@ pub fn set_sound_input(device: String) { let prior_device = get_option("audio-input".to_owned()); if prior_device != device { log::info!("switch to audio input device {}", device); - set_option("audio-input".to_owned(), device); + std::thread::spawn(move || { + set_option("audio-input".to_owned(), device); + }); } else { log::info!("audio input is already set to {}", device); } From 038d660e6063c6a8222cd7f8c2753ee07492a6e8 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 31 Jan 2023 11:10:14 +0800 Subject: [PATCH 061/117] fix: android build --- src/common.rs | 6 ++++++ src/flutter_ffi.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/common.rs b/src/common.rs index 70d50619e..3e6409c53 100644 --- a/src/common.rs +++ b/src/common.rs @@ -149,6 +149,12 @@ pub fn get_default_sound_input() -> Option { } } +#[inline] +#[cfg(any(target_os = "android", target_os = "ios"))] +pub fn get_default_sound_input() -> Option { + None +} + #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn update_clipboard(clipboard: Clipboard, old: Option<&Arc>>) { let content = if clipboard.compress { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 31cb07c07..0fe6818de 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -541,7 +541,7 @@ pub fn main_get_default_sound_input() -> Option { #[cfg(not(any(target_os = "android", target_os = "ios")))] return get_default_sound_input(); #[cfg(any(target_os = "android", target_os = "ios"))] - String::from("") + None } pub fn main_get_hostname() -> SyncReturn { From ebec8811c2ec49da7bd3f59db98d38ad0ead84a6 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 31 Jan 2023 13:32:10 +0800 Subject: [PATCH 062/117] opt: add microphone permission tip --- flutter/lib/common.dart | 27 ++++++++++++++ .../lib/desktop/pages/desktop_home_page.dart | 35 +++++++++++++++++-- flutter/macos/Runner/Info.plist | 2 ++ flutter/macos/Runner/MainFlutterWindow.swift | 18 ++++++++++ src/lang/ca.rs | 1 + src/lang/cn.rs | 1 + src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/en.rs | 3 +- src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/gr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/ua.rs | 1 + src/lang/vn.rs | 1 + 37 files changed, 114 insertions(+), 3 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 30d38b8db..df2a75f56 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1723,3 +1723,30 @@ Future updateSystemWindowTheme() async { } } } +/// macOS only +/// +/// Note: not found a general solution for rust based AVFoundation bingding. +/// [AVFoundation] crate has compile error. +const kMacOSPermChannel = MethodChannel("org.rustdesk.rustdesk/macos"); + +enum PermissionAuthorizeType { + undetermined, + authorized, + denied, // and restricted +} + +Future osxCanRecordAudio() async { + int res = await kMacOSPermChannel.invokeMethod("canRecordAudio"); + print(res); + if (res > 0) { + return PermissionAuthorizeType.authorized; + } else if (res == 0) { + return PermissionAuthorizeType.undetermined; + } else { + return PermissionAuthorizeType.denied; + } +} + +Future osxRequestAudio() async { + return await kMacOSPermChannel.invokeMethod("requestRecordAudio"); +} diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 0501c298a..71dd2c96e 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -44,6 +44,7 @@ class _DesktopHomePageState extends State var watchIsCanScreenRecording = false; var watchIsProcessTrust = false; var watchIsInputMonitoring = false; + var watchIsCanRecordAudio = false; Timer? _updateTimer; @override @@ -79,7 +80,16 @@ class _DesktopHomePageState extends State buildTip(context), buildIDBoard(context), buildPasswordBoard(context), - buildHelpCards(), + FutureBuilder( + future: buildHelpCards(), + builder: (_, data) { + if (data.hasData) { + return data.data!; + } else { + return const Offstage(); + } + }, + ), ], ), ), @@ -302,7 +312,7 @@ class _DesktopHomePageState extends State ); } - Widget buildHelpCards() { + Future buildHelpCards() async { if (updateUrl.isNotEmpty) { return buildInstallCard( "Status", @@ -348,6 +358,13 @@ class _DesktopHomePageState extends State return buildInstallCard("", "install_daemon_tip", "Install", () async { bind.mainIsInstalledDaemon(prompt: true); }); + } else if ((await osxCanRecordAudio() != + PermissionAuthorizeType.authorized)) { + return buildInstallCard("Permissions", "config_microphone", "Configure", + () async { + osxRequestAudio(); + watchIsCanRecordAudio = true; + }); } } else if (Platform.isLinux) { if (bind.mainCurrentIsWayland()) { @@ -481,6 +498,20 @@ class _DesktopHomePageState extends State setState(() {}); } } + if (watchIsCanRecordAudio) { + if (Platform.isMacOS) { + Future.microtask(() async { + if ((await osxCanRecordAudio() == + PermissionAuthorizeType.authorized)) { + watchIsCanRecordAudio = false; + setState(() {}); + } + }); + } else { + watchIsCanRecordAudio = false; + setState(() {}); + } + } }); Get.put(svcStopped, tag: 'stop-service'); rustDeskWinManager.registerActiveWindowListener(onActiveWindowChanged); diff --git a/flutter/macos/Runner/Info.plist b/flutter/macos/Runner/Info.plist index c926019ab..96616e8c4 100644 --- a/flutter/macos/Runner/Info.plist +++ b/flutter/macos/Runner/Info.plist @@ -43,6 +43,8 @@ $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu + NSMicrophoneUsageDescription + Record the sound from microphone for the purpose of the remote desktop. NSPrincipalClass NSApplication diff --git a/flutter/macos/Runner/MainFlutterWindow.swift b/flutter/macos/Runner/MainFlutterWindow.swift index 97b46bb84..21e870320 100644 --- a/flutter/macos/Runner/MainFlutterWindow.swift +++ b/flutter/macos/Runner/MainFlutterWindow.swift @@ -1,4 +1,5 @@ import Cocoa +import AVFoundation import FlutterMacOS import desktop_multi_window // import bitsdojo_window_macos @@ -81,6 +82,23 @@ class MainFlutterWindow: NSWindow { case "terminate": NSApplication.shared.terminate(self) result(nil) + case "canRecordAudio": + switch AVCaptureDevice.authorizationStatus(for: .audio) { + case .authorized: + result(1) + break + case .notDetermined: + result(0) + break + default: + result(-1) + break + } + case "requestRecordAudio": + AVCaptureDevice.requestAccess(for: .audio, completionHandler: { granted in + result(granted) + }) + break default: result(FlutterMethodNotImplemented) } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index e45dc5fb5..e65927876 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 84bfcb384..bcb2c3daf 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "如果你使用英伟达显卡, 并且远程窗口在会话建立后会立刻关闭, 那么安装 nouveau 驱动并且选择使用软件渲染可能会有帮助。重启软件后生效。"), ("Always use software rendering", "使用软件渲染"), ("config_input", "为了能够通过键盘控制远程桌面, 请给予 RustDesk \"输入监控\" 权限。"), + ("config_microphone", "为了支持通过麦克风进行音频传输,请给予 RustDesk \"录音\"权限。"), ("request_elevation_tip", "如果对面有人, 也可以请求提升权限。"), ("Wait", "等待"), ("Elevation Error", "提权失败"), diff --git a/src/lang/cs.rs b/src/lang/cs.rs index ef9cd7bf8..d16e3abef 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/da.rs b/src/lang/da.rs index 32aa1f0af..23884b995 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/de.rs b/src/lang/de.rs index f8fac0737..1839edb87 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "Wenn Sie eine Nvidia-Grafikkarte haben und sich das entfernte Fenster sofort nach dem Herstellen der Verbindung schließt, kann es helfen, den Nouveau-Treiber zu installieren und Software-Rendering zu verwenden. Ein Neustart der Software ist erforderlich."), ("Always use software rendering", "Software-Rendering immer verwenden"), ("config_input", "Um den entfernten Desktop mit der Tastatur steuern zu können, müssen Sie RustDesk \"Input Monitoring\"-Rechte erteilen."), + ("config_microphone", ""), ("request_elevation_tip", "Sie können auch erhöhte Rechte anfordern, wenn sich jemand auf der Gegenseite befindet."), ("Wait", "Warten"), ("Elevation Error", "Berechtigungsfehler"), diff --git a/src/lang/en.rs b/src/lang/en.rs index 6eed43a77..37c08a974 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -41,6 +41,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("config_input", "In order to control remote desktop with keyboard, you need to grant RustDesk \"Input Monitoring\" permissions."), ("request_elevation_tip","You can also request elevation if there is someone on the remote side."), ("wait_accept_uac_tip","Please wait for the remote user to accept the UAC dialog."), - ("still_click_uac_tip", "Still requires the remote user to click OK on the UAC window of running RustDesk.") + ("still_click_uac_tip", "Still requires the remote user to click OK on the UAC window of running RustDesk."), + ("config_microphone", "In order to speak remotely, you need to grant RustDesk \"Record Audio\" permissions.") ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 4aa2be8db..aa8829874 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/es.rs b/src/lang/es.rs index 932936da3..da13843f0 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "Si tienes una gráfica Nvidia y la ventana remota se cierra inmediatamente, instalar el driver nouveau y elegir renderizado por software podría ayudar. Se requiere reiniciar la aplicación."), ("Always use software rendering", "Usar siempre renderizado por software"), ("config_input", "Para controlar el escritorio remoto con el teclado necesitas dar a RustDesk permisos de \"Monitorización de entrada\"."), + ("config_microphone", ""), ("request_elevation_tip", "También puedes solicitar elevación si hay alguien en el lado remoto."), ("Wait", "Esperar"), ("Elevation Error", "Error de elevación"), diff --git a/src/lang/fa.rs b/src/lang/fa.rs index b8c45fbee..7664af99e 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "اگر کارت گرافیک Nvidia دارید و پنجره راه دور بلافاصله پس از اتصال بسته می شود، درایور nouveau را نصب نمایید و انتخاب گزینه استفاده از رندر نرم افزار می تواند کمک کننده باشد. راه اندازی مجدد نرم افزار مورد نیاز است."), ("Always use software rendering", "همیشه از رندر نرم افزاری استفاده کنید"), ("config_input", "برای کنترل دسکتاپ از راه دور با صفحه کلید، باید مجوز RustDesk \"Input Monitoring\" را بدهید."), + ("config_microphone", ""), ("request_elevation_tip", "همچنین می توانید در صورت وجود شخصی در سمت راه دور درخواست ارتفاع دهید."), ("Wait", "صبر کنید"), ("Elevation Error", "خطای ارتفاع"), diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 64a8b4e40..db49b5a78 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "Si vous avez une carte graphique NVIDIA et que la fenêtre distante se ferme immédiatement après la connexion, l'installation du pilote Nouveau et le choix d'utiliser le rendu du logiciel peuvent aider. Un redémarrage du logiciel est requis."), ("Always use software rendering", "Utiliser toujours le rendu logiciel"), ("config_input", "Afin de contrôler le bureau à distance avec le clavier, vous devez accorder à RustDesk l'autorisation \"Surveillance de l’entrée\"."), + ("config_microphone", ""), ("request_elevation_tip", "Vous pouvez également demander une augmentation des privilèges s'il y a quelqu'un du côté distant."), ("Wait", "En cours"), ("Elevation Error", "Erreur d'augmentation des privilèges"), diff --git a/src/lang/gr.rs b/src/lang/gr.rs index 3918db55b..5312e6381 100644 --- a/src/lang/gr.rs +++ b/src/lang/gr.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "Εάν έχετε κάρτα γραφικών Nvidia και το παράθυρο σύνδεσης κλείνει αμέσως μετά τη σύνδεση, η εγκατάσταση του προγράμματος οδήγησης nouveau και η επιλογή χρήσης της επιτάχυνσης γραφικών μέσω λογισμικού μπορεί να βοηθήσει. Απαιτείται επανεκκίνηση."), ("Always use software rendering", "Επιτάχυνση γραφικών μέσω λογισμικού"), ("config_input", "Για να ελέγξετε την απομακρυσμένη επιφάνεια εργασίας με πληκτρολόγιο, πρέπει να εκχωρήσετε δικαιώματα στο RustDesk"), + ("config_microphone", ""), ("request_elevation_tip", "αίτημα ανύψωσης δικαιωμάτων χρήστη"), ("Wait", "Περιμένετε"), ("Elevation Error", "Σφάλμα ανύψωσης δικαιωμάτων χρήστη"), diff --git a/src/lang/hu.rs b/src/lang/hu.rs index edad7ecd0..2f6c490ad 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/id.rs b/src/lang/id.rs index 1b2dc4ad5..7b9325076 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/it.rs b/src/lang/it.rs index 27432303d..31864b220 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "Se si dispone di una scheda grafica Nvidia e la finestra remota si chiude immediatamente dopo la connessione, l'installazione del driver nouveau e la scelta di utilizzare il rendering software possono aiutare. È necessario un riavvio del software."), ("Always use software rendering", "Usa sempre il render Software"), ("config_input", "Per controllare il desktop remoto con la tastiera, è necessario concedere le autorizzazioni a RustDesk \"Monitoraggio dell'input\"."), + ("config_microphone", ""), ("request_elevation_tip", "È possibile richiedere l'elevazione se c'è qualcuno sul lato remoto."), ("Wait", "Attendi"), ("Elevation Error", "Errore durante l'elevazione dei diritti"), diff --git a/src/lang/ja.rs b/src/lang/ja.rs index ae375b8ee..5f2b68c46 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 417f88fe6..59cc9fdff 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/kz.rs b/src/lang/kz.rs index e852278dd..8a939764b 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 4cce52e08..788aa8b62 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "Jeżeli posiadasz kartę graficzną Nvidia i okno zamyka się natychmiast po nawiązaniu połączenia, instalacja sterownika nouveau i wybór renderowania programowego mogą pomóc. Restart aplikacji jest wymagany."), ("Always use software rendering", "Zawsze używaj renderowania programowego"), ("config_input", "By kontrolować zdalne urządzenie przy pomocy klawiatury, musisz udzielić aplikacji RustDesk uprawnień do \"Urządzeń Wejściowych\"."), + ("config_microphone", ""), ("request_elevation_tip", "Możesz poprosić o podniesienie uprawnień jeżeli ktoś posiada dostęp do zdalnego urządzenia."), ("Wait", "Czekaj"), ("Elevation Error", "Błąd przy podnoszeniu uprawnień"), diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 29252926f..c6899ee54 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 8ec40cf13..cdac5f686 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/ro.rs b/src/lang/ro.rs index c4f798abf..5865d0206 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 949eba641..fe1de2e91 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "Если у вас видеокарта Nvidia и удалённое окно закрывается сразу после подключения, может помочь установка драйвера Nouveau и выбор использования программной визуализации. Потребуется перезапуск."), ("Always use software rendering", "Использовать программную визуализацию"), ("config_input", "Чтобы управлять удалённым рабочим столом с помощью клавиатуры, необходимо предоставить RustDesk разрешения \"Мониторинг ввода\"."), + ("config_microphone", ""), ("request_elevation_tip", "Также можно запросить повышение прав, если кто-то есть на удалённой стороне."), ("Wait", "Ждите"), ("Elevation Error", "Ошибка повышения прав"), diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 7de4d10ce..88f09313f 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/sl.rs b/src/lang/sl.rs index bf30f96d8..f78a6e9e3 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/sq.rs b/src/lang/sq.rs index db560166a..63e834c25 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 599cd651b..33355fd38 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/sv.rs b/src/lang/sv.rs index c0616300c..8af2ccb8a 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/template.rs b/src/lang/template.rs index 282b564d7..1abc20b36 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/th.rs b/src/lang/th.rs index b2bee959a..173143821 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/tr.rs b/src/lang/tr.rs index b6efeaf0a..072275334 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/tw.rs b/src/lang/tw.rs index eea71e6bf..8c0968901 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "如果你使用英偉達顯卡, 並且遠程窗口在會話建立後會立刻關閉, 那麼安裝nouveau驅動並且選擇使用軟件渲染可能會有幫助。重啟軟件後生效。"), ("Always use software rendering", "使用軟件渲染"), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", "如果對面有人, 也可以請求提升權限。"), ("Wait", "等待"), ("Elevation Error", "提權失敗"), diff --git a/src/lang/ua.rs b/src/lang/ua.rs index f0d85a551..1934a8eb4 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 5e4009570..24c0d9009 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -415,6 +415,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", ""), ("Always use software rendering", ""), ("config_input", ""), + ("config_microphone", ""), ("request_elevation_tip", ""), ("Wait", ""), ("Elevation Error", ""), From 2452a58eaa5e0d14fd5a16e135a40a9acaf547ea Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 31 Jan 2023 14:07:14 +0800 Subject: [PATCH 063/117] opt: rename and move audio transmission mode --- .../lib/desktop/widgets/remote_menubar.dart | 53 ++++++++++--------- src/lang/ca.rs | 2 + src/lang/cn.rs | 2 + src/lang/cs.rs | 2 + src/lang/da.rs | 4 +- src/lang/de.rs | 2 + src/lang/eo.rs | 2 + src/lang/es.rs | 4 +- src/lang/fa.rs | 2 + src/lang/fr.rs | 2 + src/lang/gr.rs | 2 + src/lang/hu.rs | 2 + src/lang/id.rs | 2 + src/lang/it.rs | 2 + src/lang/ja.rs | 2 + src/lang/ko.rs | 2 + src/lang/kz.rs | 2 + src/lang/pl.rs | 2 + src/lang/pt_PT.rs | 2 + src/lang/ptbr.rs | 2 + src/lang/ro.rs | 2 + src/lang/ru.rs | 4 +- src/lang/sk.rs | 2 + src/lang/sl.rs | 4 +- src/lang/sq.rs | 2 + src/lang/sr.rs | 2 + src/lang/sv.rs | 2 + src/lang/template.rs | 2 + src/lang/th.rs | 2 + src/lang/tr.rs | 2 + src/lang/tw.rs | 2 + src/lang/ua.rs | 2 + src/lang/vn.rs | 2 + 33 files changed, 91 insertions(+), 34 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 9864947c6..0df962cba 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -884,7 +884,33 @@ class _RemoteMenubarState extends State { // )); // } } - + displayMenu.addAll([ + MenuEntryDivider(), + MenuEntryRadios( + text: translate('Audio Transmission Mode'), + optionsGetter: () => [ + MenuEntryRadioOption( + text: translate('Guest to host audio transmission'), + value: kRemoteAudioGuestToHost, + dismissOnClicked: true, + ), + MenuEntryRadioOption( + text: translate('Dual-way audio transmission'), + value: kRemoteAudioDualWay, + dismissOnClicked: true, + ), + ], + curOptionGetter: () async => + // null means peer id is not found, which there's no need to care about + await bind.sessionGetAudioMode(id: widget.id) ?? '', + optionSetter: (String oldValue, String newValue) async { + if (oldValue != newValue) { + await bind.sessionSetAudioMode(id: widget.id, value: newValue); + } + }, + padding: padding, + ), + ]); return displayMenu; } @@ -1106,31 +1132,6 @@ class _RemoteMenubarState extends State { padding: padding, ), MenuEntryDivider(), - MenuEntryRadios( - text: translate('Audio Transmission Mode'), - optionsGetter: () => [ - MenuEntryRadioOption( - text: translate('Guest to Host'), - value: kRemoteAudioGuestToHost, - dismissOnClicked: true, - ), - MenuEntryRadioOption( - text: translate('Dual way'), - value: kRemoteAudioDualWay, - dismissOnClicked: true, - ), - ], - curOptionGetter: () async => - // null means peer id is not found, which there's no need to care about - await bind.sessionGetAudioMode(id: widget.id) ?? '', - optionSetter: (String oldValue, String newValue) async { - if (oldValue != newValue) { - await bind.sessionSetAudioMode(id: widget.id, value: newValue); - } - }, - padding: padding, - ), - MenuEntryDivider(), ]; if (widget.state.viewStyle.value == kRemoteViewStyleOriginal) { diff --git a/src/lang/ca.rs b/src/lang/ca.rs index e65927876..4404e178d 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index bcb2c3daf..08f6824c7 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", "其它默认选项"), ("Guest to Host", "被控到主机"), ("Dual way", "双向"), + ("Guest to host audio transmission", "被控到主机音频传输"), + ("Dual-way audio transmission", "双向音频传输"), ("Audio Transmission Mode", "音频传输模式"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index d16e3abef..a2a19a37a 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 23884b995..905f4814e 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -437,8 +437,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Switch Sides", ""), ("Please confirm if you want to share your desktop?", ""), ("Closed as expected", ""), - ("Guest to Host", ""), - ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ("Display", ""), ("Default View Style", ""), diff --git a/src/lang/de.rs b/src/lang/de.rs index 1839edb87..4028e3337 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", "Weitere Standardoptionen"), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index aa8829874..fe3830b99 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index da13843f0..b9b31f109 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -447,8 +447,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Auto", ""), ("Other Default Options", "Otras opciones predeterminadas"), ("Closed as expected", ""), - ("Guest to Host", ""), - ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 7664af99e..0b92c6658 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", "سایر گزینه های پیش فرض"), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index db49b5a78..4965f6dab 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", "Autres options par défaut"), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/gr.rs b/src/lang/gr.rs index 5312e6381..e40151ccf 100644 --- a/src/lang/gr.rs +++ b/src/lang/gr.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 2f6c490ad..0e1887e48 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 7b9325076..689ae98cf 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 31864b220..65f91ecec 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", "Altre Opzioni Predefinite"), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 5f2b68c46..33fb2da05 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 59cc9fdff..c874dd695 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 8a939764b..01014bab0 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 788aa8b62..9dd005bdd 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", "Inne opcje domyślne"), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index c6899ee54..716d3df82 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index cdac5f686..c7d0cd6ec 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 5865d0206..2d48b91b4 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index fe1de2e91..8224cd5eb 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -448,8 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", "Другие параметры по умолчанию"), ("Please confirm if you want to share your desktop?", "Подтвердите, что хотите поделиться своим рабочим столом?"), ("Closed as expected", ""), - ("Guest to Host", ""), - ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 88f09313f..5e0330954 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index f78a6e9e3..a75da46bd 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -446,8 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 63e834c25..d3964a2e9 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 33355fd38..78059645d 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 8af2ccb8a..ca2257756 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 1abc20b36..4355d643a 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 173143821..57dfe6e43 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 072275334..49a42af4a 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 8c0968901..50e684258 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", "其它默認選項"), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 1934a8eb4..f37ed341e 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 24c0d9009..5788a7f3d 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -448,6 +448,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Other Default Options", ""), ("Guest to Host", ""), ("Dual way", ""), + ("Guest to host audio transmission", ""), + ("Dual way audio transmission", ""), ("Audio Transmission Mode", ""), ].iter().cloned().collect(); } From efa4530c97f6eee9c8c8dcd36188218ada8e52f1 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 31 Jan 2023 22:49:17 +0800 Subject: [PATCH 064/117] feat: add chat svg --- flutter/assets/chat.svg | 1 + .../lib/desktop/widgets/remote_menubar.dart | 96 +++++++++++-------- src/lang/cn.rs | 2 + 3 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 flutter/assets/chat.svg diff --git a/flutter/assets/chat.svg b/flutter/assets/chat.svg new file mode 100644 index 000000000..03491be6e --- /dev/null +++ b/flutter/assets/chat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 0df962cba..0004c65fe 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -9,6 +9,7 @@ import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:debounce_throttle/debounce_throttle.dart'; @@ -478,20 +479,6 @@ class _RemoteMenubarState extends State { ); } - Widget _buildChat(BuildContext context) { - return IconButton( - tooltip: translate('Chat'), - onPressed: () { - widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID); - widget.ffi.chatModel.toggleChatOverlay(); - }, - icon: const Icon( - Icons.message, - color: _MenubarTheme.commonColor, - ), - ); - } - Widget _buildMonitor(BuildContext context) { final pi = widget.ffi.ffiModel.pi; return mod_menu.PopupMenuButton( @@ -695,6 +682,60 @@ class _RemoteMenubarState extends State { ); } + Widget _buildChat(BuildContext context) { + FfiModel ffiModel = Provider.of(context); + return mod_menu.PopupMenuButton( + padding: EdgeInsets.zero, + icon: SvgPicture.asset( + "assets/chat.svg", + color: _MenubarTheme.commonColor, + width: Theme.of(context).iconTheme.size ?? 24.0, + height: Theme.of(context).iconTheme.size ?? 24.0, + ), + tooltip: translate('Chat'), + position: mod_menu.PopupMenuPosition.under, + itemBuilder: (BuildContext context) => _getChatMenu(context) + .map((entry) => entry.build( + context, + const MenuConfig( + commonColor: _MenubarTheme.commonColor, + height: _MenubarTheme.height, + dividerHeight: _MenubarTheme.dividerHeight, + ))) + .expand((i) => i) + .toList(), + ); + } + + List> _getChatMenu(BuildContext context) { + final List> chatMenu = []; + const EdgeInsets padding = EdgeInsets.only(left: 14.0, right: 5.0); + chatMenu.addAll([ + MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Text chat'), + style: style, + ), + proc: () { + widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID); + widget.ffi.chatModel.toggleChatOverlay(); + }, + padding: padding, + dismissOnClicked: true, + ), + MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Voice call'), + style: style, + ), + proc: () {}, + padding: padding, + dismissOnClicked: true, + ), + ]); + return chatMenu; + } + List> _getControlMenu(BuildContext context) { final pi = widget.ffi.ffiModel.pi; final perms = widget.ffi.ffiModel.permissions; @@ -884,33 +925,6 @@ class _RemoteMenubarState extends State { // )); // } } - displayMenu.addAll([ - MenuEntryDivider(), - MenuEntryRadios( - text: translate('Audio Transmission Mode'), - optionsGetter: () => [ - MenuEntryRadioOption( - text: translate('Guest to host audio transmission'), - value: kRemoteAudioGuestToHost, - dismissOnClicked: true, - ), - MenuEntryRadioOption( - text: translate('Dual-way audio transmission'), - value: kRemoteAudioDualWay, - dismissOnClicked: true, - ), - ], - curOptionGetter: () async => - // null means peer id is not found, which there's no need to care about - await bind.sessionGetAudioMode(id: widget.id) ?? '', - optionSetter: (String oldValue, String newValue) async { - if (oldValue != newValue) { - await bind.sessionSetAudioMode(id: widget.id, value: newValue); - } - }, - padding: padding, - ), - ]); return displayMenu; } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 08f6824c7..65039f0fe 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -450,6 +450,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Dual way", "双向"), ("Guest to host audio transmission", "被控到主机音频传输"), ("Dual-way audio transmission", "双向音频传输"), + ("Voice call", "语音通话"), + ("Text chat", "文字聊天"), ("Audio Transmission Mode", "音频传输模式"), ].iter().cloned().collect(); } From b335d2c82840dd3ef09efc9ebdd7d417ca3a9a25 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sun, 5 Feb 2023 15:36:31 +0800 Subject: [PATCH 065/117] fix: import --- src/client/io_loop.rs | 1 - src/flutter_ffi.rs | 3 --- src/server.rs | 7 ------- 3 files changed, 11 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 9117c8c5f..dcfa7b74e 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -12,7 +12,6 @@ use clipboard::{cliprdr::CliprdrClientContext, ContextSend}; use hbb_common::tokio::sync::mpsc::error::TryRecvError; -use crate::server::Service; use crate::ui_session_interface::{InvokeUiSession, Session}; use crate::{client::Data, client::Interface}; diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 0fe6818de..1ecbb0646 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -11,15 +11,12 @@ use hbb_common::{ config::{self, LocalConfig, ONLINE, PeerConfig}, fs, log, }; -use hbb_common::message_proto::KeyboardMode; -use hbb_common::ResultType; use crate::{ client::file_trait::FileManager, common::make_fd_to_json, flutter::{session_add, session_start_}, }; -use crate::common::is_keyboard_mode_supported; use crate::flutter::{self, SESSIONS}; use crate::ui_interface::{self, *}; diff --git a/src/server.rs b/src/server.rs index bef49f132..616d92375 100644 --- a/src/server.rs +++ b/src/server.rs @@ -29,13 +29,6 @@ use service::{GenericService, Service, Subscriber}; use service::ServiceTmpl; use crate::ipc::{connect, Data}; -pub use service::{GenericService, Service, ServiceTmpl, Subscriber}; -use std::{ - collections::HashMap, - net::SocketAddr, - sync::{Arc, Mutex, RwLock, Weak}, - time::Duration, -}; pub mod audio_service; cfg_if::cfg_if! { From 45b93100d6d0837d53d12dca30605cc0b10b1ea4 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sun, 5 Feb 2023 23:47:06 +0800 Subject: [PATCH 066/117] feat: add voice call proto --- libs/hbb_common/protos/message.proto | 14 ++++++ src/client.rs | 49 +++++++++++---------- src/client/io_loop.rs | 64 ++++++++++++++++++---------- src/flutter_ffi.rs | 18 ++++++-- src/server/connection.rs | 6 +++ src/ui/remote.rs | 8 ++-- src/ui_session_interface.rs | 48 +++++++++++++-------- 7 files changed, 138 insertions(+), 69 deletions(-) diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 48b999438..323b464fa 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -604,6 +604,18 @@ message Misc { } } +message VoiceCallRequest { + int64 req_timestamp = 1; + // Indicates whether the request is a connect action or a disconnect action. + bool is_connect = 2; +} + +message VoiceCallResponse { + bool accepted = 1; + int64 req_timestamp = 2; // Should copy from [VoiceCallRequest::req_timestamp]. + int64 ack_timestamp = 3; +} + message Message { oneof union { SignedId signed_id = 3; @@ -626,5 +638,7 @@ message Message { Cliprdr cliprdr = 20; MessageBox message_box = 21; SwitchSidesResponse switch_sides_response = 22; + VoiceCallRequest voice_call_request = 23; + VoiceCallResponse voice_call_response = 24; } } diff --git a/src/client.rs b/src/client.rs index 649b180bb..5911c40ed 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,58 +1,61 @@ -pub use async_trait::async_trait; -use bytes::Bytes; -#[cfg(not(any(target_os = "android", target_os = "linux")))] -use cpal::{ - traits::{DeviceTrait, HostTrait, StreamTrait}, - Device, Host, StreamConfig, -}; -use magnum_opus::{Channels::*, Decoder as AudioDecoder}; -use sha2::{Digest, Sha256}; use std::{ collections::HashMap, net::SocketAddr, ops::{Deref, Not}, str::FromStr, - sync::{atomic::AtomicBool, mpsc, Arc, Mutex, RwLock}, + sync::{Arc, atomic::AtomicBool, mpsc, Mutex, RwLock}, }; + +pub use async_trait::async_trait; +use bytes::Bytes; +#[cfg(not(any(target_os = "android", target_os = "linux")))] +use cpal::{ + Device, + Host, StreamConfig, traits::{DeviceTrait, HostTrait, StreamTrait}, +}; +use magnum_opus::{Channels::*, Decoder as AudioDecoder}; +use sha2::{Digest, Sha256}; use uuid::Uuid; pub use file_trait::FileManager; use hbb_common::{ + AddrMangle, allow_err, anyhow::{anyhow, Context}, bail, config::{ - Config, PeerConfig, PeerInfoSerde, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT, + Config, CONNECT_TIMEOUT, PeerConfig, PeerInfoSerde, READ_TIMEOUT, RELAY_PORT, RENDEZVOUS_TIMEOUT, - }, - get_version_number, log, - message_proto::{option_message::BoolOption, *}, + }, get_version_number, + log, + message_proto::{*, option_message::BoolOption}, protobuf::Message as _, rand, rendezvous_proto::*, + ResultType, socket_client, sodiumoxide::crypto::{box_, secretbox, sign}, - timeout, - tokio::time::Duration, - AddrMangle, ResultType, Stream, + Stream, timeout, tokio::time::Duration, }; -pub use helper::LatencyController; pub use helper::*; +pub use helper::LatencyController; use scrap::{ codec::{Decoder, DecoderCfg}, record::{Recorder, RecorderContext}, VpxDecoderConfig, VpxVideoCodecId, }; +use crate::{ + common::{self, is_keyboard_mode_supported}, + server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED}, +}; + pub use super::lang::*; pub mod file_trait; pub mod helper; pub mod io_loop; -use crate::{ - common::{self, is_keyboard_mode_supported}, - server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED}, -}; + pub static SERVER_KEYBOARD_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_FILE_TRANSFER_ENABLED: AtomicBool = AtomicBool::new(true); pub static SERVER_CLIPBOARD_ENABLED: AtomicBool = AtomicBool::new(true); @@ -1989,6 +1992,8 @@ pub enum Data { RecordScreen(bool, i32, i32, String), ElevateDirect, ElevateWithLogon(String, String), + NewVoiceCall, + CloseVoiceCall, } /// Keycode for key events. diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index dcfa7b74e..67946f546 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1,42 +1,38 @@ -use crate::client::{ - Client, CodecFormat, LoginConfigHandler, MediaData, MediaSender, QualityStatus, MILLI1, SEC30, - SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED, SERVER_KEYBOARD_ENABLED, -}; -use crate::common::{get_default_sound_input, set_sound_input}; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::common::{check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL}; -use crate::{audio_service, common, ConnInner, CLIENT_SERVER}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use std::sync::atomic::{AtomicUsize, Ordering}; #[cfg(windows)] use clipboard::{cliprdr::CliprdrClientContext, ContextSend}; - -use hbb_common::tokio::sync::mpsc::error::TryRecvError; - -use crate::ui_session_interface::{InvokeUiSession, Session}; -use crate::{client::Data, client::Interface}; - +use hbb_common::{allow_err, message_proto::*, sleep, get_time}; +use hbb_common::{fs, log, Stream}; use hbb_common::config::{PeerConfig, TransferSerde}; use hbb_common::fs::{ - can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult, + can_enable_overwrite_detection, DigestCheckResult, get_job, get_string, new_send_confirm, RemoveJobMeta, }; use hbb_common::message_proto::permission_info::Permission; use hbb_common::protobuf::Message as _; use hbb_common::rendezvous_proto::ConnType; -#[cfg(windows)] -use hbb_common::tokio::sync::Mutex as TokioMutex; use hbb_common::tokio::{ self, sync::mpsc, time::{self, Duration, Instant, Interval}, }; -use hbb_common::{allow_err, message_proto::*, sleep}; -use hbb_common::{fs, log, Stream}; +use hbb_common::tokio::sync::mpsc::error::TryRecvError; +#[cfg(windows)] +use hbb_common::tokio::sync::Mutex as TokioMutex; -use std::collections::HashMap; - -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; +use crate::{audio_service, CLIENT_SERVER, common, ConnInner}; +use crate::{client::Data, client::Interface}; +use crate::client::{ + Client, CodecFormat, LoginConfigHandler, MediaData, MediaSender, MILLI1, QualityStatus, SEC30, + SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED, SERVER_KEYBOARD_ENABLED, +}; +use crate::common::{get_default_sound_input, set_sound_input}; +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use crate::common::{check_clipboard, CLIPBOARD_INTERVAL, ClipboardContext, update_clipboard}; +use crate::ui_session_interface::{InvokeUiSession, Session}; pub struct Remote { handler: Session, @@ -752,6 +748,22 @@ impl Remote { msg.set_misc(misc); allow_err!(peer.send(&msg).await); } + Data::NewVoiceCall => { + let mut request = VoiceCallRequest::new(); + request.is_connect = true; + request.req_timestamp = get_time(); + let mut msg = Message::new(); + msg.set_voice_call_request(request); + allow_err!(peer.send(&msg).await); + } + Data::CloseVoiceCall => { + let mut request = VoiceCallRequest::new(); + request.is_connect = false; + request.req_timestamp = get_time(); + let mut msg = Message::new(); + msg.set_voice_call_request(request); + allow_err!(peer.send(&msg).await); + } _ => {} } true @@ -1262,6 +1274,12 @@ impl Remote { self.handler .msgbox(&msgbox.msgtype, &msgbox.title, &msgbox.text, &link); } + Some(message::Union::VoiceCallRequest(request)) => { + // TODO + } + Some(message::Union::VoiceCallResponse(response)) => { + // TODO + } _ => {} } } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 1ecbb0646..15bfe90d4 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -4,19 +4,19 @@ use std::str::FromStr; use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer}; use serde_json::json; -use crate::common::{is_keyboard_mode_supported, get_default_sound_input}; -use hbb_common::message_proto::KeyboardMode; -use hbb_common::ResultType; use hbb_common::{ config::{self, LocalConfig, ONLINE, PeerConfig}, fs, log, }; +use hbb_common::message_proto::KeyboardMode; +use hbb_common::ResultType; use crate::{ client::file_trait::FileManager, common::make_fd_to_json, flutter::{session_add, session_start_}, }; +use crate::common::{get_default_sound_input, is_keyboard_mode_supported}; use crate::flutter::{self, SESSIONS}; use crate::ui_interface::{self, *}; @@ -840,6 +840,18 @@ pub fn session_new_rdp(id: String) { } } +pub fn session_request_voice_call(id: String) { + if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { + session.request_voice_call(); + } +} + +pub fn session_close_voice_call(id: String) { + if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { + session.close_voice_call(); + } +} + pub fn main_get_last_remote_id() -> String { LocalConfig::get_remote_id() } diff --git a/src/server/connection.rs b/src/server/connection.rs index 20cbe0f86..c3acae9cc 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1572,6 +1572,12 @@ impl Connection { allow_err!(self.audio_sender.send(MediaData::AudioFrame(frame))); } } + Some(message::Union::VoiceCallRequest(request)) => { + // TODO + } + Some(message::Union::VoiceCallResponse(response)) => { + // TODO + } _ => {} } } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 541d3a141..1b0d172b9 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -6,12 +6,12 @@ use std::{ use sciter::{ dom::{ - event::{EventReason, BEHAVIOR_EVENTS, EVENT_GROUPS, PHASE_MASK}, - Element, HELEMENT, + Element, + event::{BEHAVIOR_EVENTS, EVENT_GROUPS, EventReason, PHASE_MASK}, HELEMENT, }, make_args, - video::{video_destination, AssetPtr, COLOR_SPACE}, Value, + video::{AssetPtr, COLOR_SPACE, video_destination}, }; use hbb_common::{ @@ -422,6 +422,8 @@ impl sciter::EventHandler for SciterSession { fn restart_remote_device(); fn save_audio_mode(String); fn get_audio_mode(); + fn request_voice_call(); + fn close_voice_call(); } } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 1e7848505..147cd9149 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -1,26 +1,30 @@ -use crate::client::io_loop::Remote; -use crate::client::{ - check_if_retry, handle_hash, handle_login_error, handle_login_from_ui, handle_test_delay, - input_os_password, load_config, send_mouse, start_video_audio_threads, FileManager, Key, - LoginConfigHandler, QualityStatus, KEY_MAP, -}; -use crate::common::{self, GrabState}; -use crate::keyboard; -use crate::{client::Data, client::Interface}; -use async_trait::async_trait; -use bytes::Bytes; -use hbb_common::config::{Config, LocalConfig, PeerConfig, RS_PUB_KEY}; -use hbb_common::rendezvous_proto::ConnType; -use hbb_common::tokio::{self, sync::mpsc}; -use hbb_common::{allow_err, message_proto::*}; -use hbb_common::{fs, get_version_number, log, Stream}; -use rdev::{Event, EventType::*}; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use std::str::FromStr; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, RwLock}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +use async_trait::async_trait; +use bytes::Bytes; +use rdev::{Event, EventType::*}; use uuid::Uuid; + +use hbb_common::{allow_err, message_proto::*}; +use hbb_common::{fs, get_version_number, log, Stream}; +use hbb_common::config::{Config, LocalConfig, PeerConfig, RS_PUB_KEY}; +use hbb_common::rendezvous_proto::ConnType; +use hbb_common::tokio::{self, sync::mpsc}; + +use crate::{client::Data, client::Interface}; +use crate::client::{ + check_if_retry, FileManager, handle_hash, handle_login_error, handle_login_from_ui, + handle_test_delay, input_os_password, Key, KEY_MAP, load_config, LoginConfigHandler, + QualityStatus, send_mouse, start_video_audio_threads, +}; +use crate::client::io_loop::Remote; +use crate::common::{self, GrabState}; +use crate::keyboard; + pub static IS_IN: AtomicBool = AtomicBool::new(false); #[derive(Clone, Default)] @@ -669,6 +673,14 @@ impl Session { } } } + + pub fn request_voice_call(&self) { + self.send(Data::NewVoiceCall); + } + + pub fn close_voice_call(&self) { + self.send(Data::CloseVoiceCall); + } } pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { From a04980fa1325a2da1a2625983b1aa016a3153187 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 6 Feb 2023 09:37:52 +0800 Subject: [PATCH 067/117] refactor: remove audio mode --- libs/hbb_common/protos/message.proto | 6 ----- src/client.rs | 40 ---------------------------- src/client/io_loop.rs | 36 ------------------------- src/flutter_ffi.rs | 14 ---------- src/ui/header.tis | 5 ---- src/ui/remote.rs | 2 -- src/ui_session_interface.rs | 16 ----------- 7 files changed, 119 deletions(-) diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 323b464fa..ed2706382 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -444,11 +444,6 @@ enum ImageQuality { Best = 4; } -enum AudioMode { - GuestToHost = 0; - DualWay = 1; -} - message VideoCodecState { enum PreferCodec { Auto = 0; @@ -480,7 +475,6 @@ message OptionMessage { BoolOption enable_file_transfer = 9; VideoCodecState video_codec_state = 10; int32 custom_fps = 11; - AudioMode audio_mode = 12; } message TestDelay { diff --git a/src/client.rs b/src/client.rs index 5911c40ed..2ea33b655 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1255,27 +1255,6 @@ impl LoginConfigHandler { } } - /// Parse the audio mode option. - /// Return [`AudioMode`] if the option is valid, otherwise return `None`. - /// - /// # Arguments - /// - /// * `q` - The audio mode option. - /// * `ignore_default` - Ignore the default value. - pub fn get_audio_mode_enum(q: &str, ignore_default: bool) -> Option { - if q == "guest-to-host" { - Some(AudioMode::GuestToHost) - } else if q == "dual-way" { - Some(AudioMode::DualWay) - } else { - if ignore_default { - None - } else { - Some(AudioMode::GuestToHost) - } - } - } - /// Get the status of a toggle option. /// /// # Arguments @@ -1362,24 +1341,6 @@ impl LoginConfigHandler { res } - pub fn save_audio_mode(&mut self, value: String) -> Option { - let mut res = None; - if let Some(q) = LoginConfigHandler::get_audio_mode_enum(&value, false) { - let mut misc = Misc::new(); - misc.set_option(OptionMessage { - audio_mode: q.into(), - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - res = Some(msg_out); - } - let mut config = self.load_config(); - config.audio_mode = value; - self.save_config(config); - res - } - /// Create a [`Message`] for saving custom fps. /// /// # Arguments @@ -1984,7 +1945,6 @@ pub enum Data { RemovePortForward(i32), AddPortForward((i32, String, i32)), ToggleClipboardFile, - ChangeAudioMode(AudioMode), NewRDP, SetConfirmOverrideFile((i32, i32, bool, bool, bool)), AddJob((i32, String, String, i32, bool, bool)), diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 67946f546..d0e72a7e6 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -387,24 +387,6 @@ impl Remote { Data::ToggleClipboardFile => { self.check_clipboard_file_context(); } - Data::ChangeAudioMode(audio_mode) => { - match audio_mode { - AudioMode::GuestToHost => { - if let Some(sender) = self.stop_local_audio_sender.take() { - allow_err!(sender.send(())); - } - } - AudioMode::DualWay => { - // Start audio thread for playback. - // Cancel previous local audio session. - if let Some(sender) = self.stop_local_audio_sender.take() { - allow_err!(sender.send(())); - } - // Start client audio when connection is established. - self.stop_local_audio_sender = self.start_client_audio(); - } - } - } Data::Message(msg) => { allow_err!(peer.send(&msg).await); } @@ -905,24 +887,6 @@ impl Remote { if self.handler.is_file_transfer() { self.handler.load_last_jobs(); } - - // Start audio thread for playback if current audio mode is dual-way transmission. - if !self.handler.is_file_transfer() && !self.handler.is_port_forward() { - let audio_mode = LoginConfigHandler::get_audio_mode_enum( - self.handler.load_config().audio_mode.as_str(), - false, - ) - .unwrap_or(AudioMode::GuestToHost); - log::debug!("current audio mode: {:?}", audio_mode); - if audio_mode == AudioMode::DualWay { - // Cancel previous local audio session. - if let Some(sender) = self.stop_local_audio_sender.take() { - allow_err!(sender.send(())); - } - // Start client audio when connection is established. - self.stop_local_audio_sender = self.start_client_audio(); - } - } } _ => {} }, diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 15bfe90d4..e28332943 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -233,20 +233,6 @@ pub fn session_set_image_quality(id: String, value: String) { } } -pub fn session_get_audio_mode(id: String) -> Option { - if let Some(session) = SESSIONS.read().unwrap().get(&id) { - Some(session.get_audio_mode()) - } else { - None - } -} - -pub fn session_set_audio_mode(id: String, value: String) { - if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { - session.save_audio_mode(value); - } -} - pub fn session_get_keyboard_mode(id: String) -> Option { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_keyboard_mode()) diff --git a/src/ui/header.tis b/src/ui/header.tis index e3f0c70a1..009995f4f 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -183,9 +183,6 @@ class Header: Reactor.Component {
  • {svg_checkmark}{translate('Balanced')}
  • {svg_checkmark}{translate('Optimize reaction time')}
  • {svg_checkmark}{translate('Custom')}
  • -
    -
  • {svg_checkmark}{translate('Guest to Host')}
  • -
  • {svg_checkmark}{translate('Dual way')}
  • {show_codec ?
  • {svg_checkmark}Auto
  • @@ -394,8 +391,6 @@ class Header: Reactor.Component { } else if (type == "codec-preference") { handler.set_option("codec-preference", me.id); handler.change_prefer_codec(); - } else if (type == "audio-mode") { - handler.save_audio_mode(me.id); } toggleMenuState(); } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 1b0d172b9..5d6692c3b 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -420,8 +420,6 @@ impl sciter::EventHandler for SciterSession { fn supported_hwcodec(); fn change_prefer_codec(); fn restart_remote_device(); - fn save_audio_mode(String); - fn get_audio_mode(); fn request_voice_call(); fn close_voice_call(); } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 147cd9149..2f6827523 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -93,22 +93,6 @@ impl Session { self.lc.write().unwrap().save_keyboard_mode(value); } - pub fn get_audio_mode(&self) -> String { - self.lc.read().unwrap().audio_mode.clone() - } - - pub fn save_audio_mode(&self, value: String) { - let mode = LoginConfigHandler::get_audio_mode_enum(value.as_str(), false); - if let Some(mode)= mode { - self.send(Data::ChangeAudioMode(mode)); - } - let msg = self.lc.write().unwrap().save_audio_mode(value); - // Notify remote guest that the audio mode has been changed. - if let Some(msg) = msg { - self.send(Data::Message(msg)); - } - } - pub fn save_view_style(&mut self, value: String) { self.lc.write().unwrap().save_view_style(value); } From b412a7122b837dd3d9d31c29f04ffc237356d97c Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 6 Feb 2023 11:42:25 +0800 Subject: [PATCH 068/117] feat: rust connection implementation --- src/client/helper.rs | 23 +++++- src/client/io_loop.rs | 85 ++++++++++++-------- src/flutter.rs | 16 ++++ src/ipc.rs | 5 +- src/lang/cn.rs | 1 + src/server/connection.rs | 151 ++++++++++++++++++++++++------------ src/ui/remote.rs | 16 ++++ src/ui_session_interface.rs | 4 + 8 files changed, 220 insertions(+), 81 deletions(-) diff --git a/src/client/helper.rs b/src/client/helper.rs index e3acf3a44..20acd811a 100644 --- a/src/client/helper.rs +++ b/src/client/helper.rs @@ -5,7 +5,7 @@ use std::{ use hbb_common::{ log, - message_proto::{video_frame, VideoFrame}, + message_proto::{video_frame, VideoFrame, Message, VoiceCallRequest, VoiceCallResponse}, get_time, }; const MAX_LATENCY: i64 = 500; @@ -115,3 +115,24 @@ pub struct QualityStatus { pub target_bitrate: Option, pub codec_format: Option, } + +#[inline] +pub fn new_voice_call_request(is_connect: bool) -> Message { + let mut req = VoiceCallRequest::new(); + req.is_connect = is_connect; + req.req_timestamp = get_time(); + let mut msg = Message::new(); + msg.set_voice_call_request(req); + msg +} + +#[inline] +pub fn new_voice_call_response(request_timestamp: i64, accepted: bool) -> Message { + let mut resp = VoiceCallResponse::new(); + resp.accepted = accepted; + resp.req_timestamp = request_timestamp; + resp.ack_timestamp = get_time(); + let mut msg = Message::new(); + msg.set_voice_call_response(resp); + msg +} \ No newline at end of file diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index d0e72a7e6..8f2b45321 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1,38 +1,40 @@ use std::collections::HashMap; -use std::sync::{Arc, Mutex}; +use std::num::NonZeroI64; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; #[cfg(windows)] use clipboard::{cliprdr::CliprdrClientContext, ContextSend}; -use hbb_common::{allow_err, message_proto::*, sleep, get_time}; -use hbb_common::{fs, log, Stream}; use hbb_common::config::{PeerConfig, TransferSerde}; use hbb_common::fs::{ - can_enable_overwrite_detection, DigestCheckResult, get_job, get_string, new_send_confirm, + can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult, RemoveJobMeta, }; use hbb_common::message_proto::permission_info::Permission; use hbb_common::protobuf::Message as _; use hbb_common::rendezvous_proto::ConnType; +use hbb_common::tokio::sync::mpsc::error::TryRecvError; +#[cfg(windows)] +use hbb_common::tokio::sync::Mutex as TokioMutex; use hbb_common::tokio::{ self, sync::mpsc, time::{self, Duration, Instant, Interval}, }; -use hbb_common::tokio::sync::mpsc::error::TryRecvError; -#[cfg(windows)] -use hbb_common::tokio::sync::Mutex as TokioMutex; +use hbb_common::{allow_err, get_time, message_proto::*, sleep}; +use hbb_common::{fs, log, Stream}; -use crate::{audio_service, CLIENT_SERVER, common, ConnInner}; -use crate::{client::Data, client::Interface}; use crate::client::{ - Client, CodecFormat, LoginConfigHandler, MediaData, MediaSender, MILLI1, QualityStatus, SEC30, - SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED, SERVER_KEYBOARD_ENABLED, + new_voice_call_request, Client, CodecFormat, LoginConfigHandler, MediaData, MediaSender, + QualityStatus, MILLI1, SEC30, SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED, + SERVER_KEYBOARD_ENABLED, }; -use crate::common::{get_default_sound_input, set_sound_input}; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::common::{check_clipboard, CLIPBOARD_INTERVAL, ClipboardContext, update_clipboard}; +use crate::common::{check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL}; +use crate::common::{get_default_sound_input, set_sound_input}; use crate::ui_session_interface::{InvokeUiSession, Session}; +use crate::{audio_service, common, ConnInner, CLIENT_SERVER}; +use crate::{client::Data, client::Interface}; pub struct Remote { handler: Session, @@ -41,7 +43,8 @@ pub struct Remote { receiver: mpsc::UnboundedReceiver, sender: mpsc::UnboundedSender, // Stop sending local audio to remote client. - stop_local_audio_sender: Option>, + stop_voice_call_sender: Option>, + voice_call_request_timestamp: Option, old_clipboard: Arc>, read_jobs: Vec, write_jobs: Vec, @@ -83,7 +86,8 @@ impl Remote { data_count: Arc::new(AtomicUsize::new(0)), frame_count, video_format: CodecFormat::Unknown, - stop_local_audio_sender: None, + stop_voice_call_sender: None, + voice_call_request_timestamp: None, } } @@ -217,7 +221,7 @@ impl Remote { } log::debug!("Exit io_loop of id={}", self.handler.id); // Stop client audio server. - if let Some(s) = self.stop_local_audio_sender.take() { + if let Some(s) = self.stop_voice_call_sender.take() { s.send(()).ok(); } } @@ -261,8 +265,15 @@ impl Remote { } } - // Start a local audio recorder, records audio and send to remote - fn start_client_audio(&mut self) -> Option> { + fn stop_voice_call(&mut self) { + let voice_call_sender = std::mem::replace(&mut self.stop_voice_call_sender, None); + if let Some(stopper) = voice_call_sender { + let _ = stopper.send(()); + } + } + + // Start a voice call recorder, records audio and send to remote + fn start_voice_call(&mut self) -> Option> { if self.handler.is_file_transfer() || self.handler.is_port_forward() { return None; } @@ -731,19 +742,17 @@ impl Remote { allow_err!(peer.send(&msg).await); } Data::NewVoiceCall => { - let mut request = VoiceCallRequest::new(); - request.is_connect = true; - request.req_timestamp = get_time(); - let mut msg = Message::new(); - msg.set_voice_call_request(request); + let msg = new_voice_call_request(true); + // Save the voice call request timestamp for the further validation. + self.voice_call_request_timestamp = Some( + NonZeroI64::new(msg.voice_call_request().req_timestamp) + .unwrap_or(NonZeroI64::new(get_time()).unwrap()), + ); allow_err!(peer.send(&msg).await); } Data::CloseVoiceCall => { - let mut request = VoiceCallRequest::new(); - request.is_connect = false; - request.req_timestamp = get_time(); - let mut msg = Message::new(); - msg.set_voice_call_request(request); + self.stop_voice_call(); + let msg = new_voice_call_request(false); allow_err!(peer.send(&msg).await); } _ => {} @@ -1238,11 +1247,25 @@ impl Remote { self.handler .msgbox(&msgbox.msgtype, &msgbox.title, &msgbox.text, &link); } - Some(message::Union::VoiceCallRequest(request)) => { - // TODO + Some(message::Union::VoiceCallRequest(_request)) => { + // TODO: maybe we will do voice call from the peer. } Some(message::Union::VoiceCallResponse(response)) => { - // TODO + let ts = std::mem::replace(&mut self.voice_call_request_timestamp, None); + if let Some(ts) = ts { + if response.req_timestamp != ts.get() { + log::debug!("Possible encountering a voice call attack."); + } else { + if response.accepted { + // The peer accepts the voice call. + self.handler.on_voice_call_start(); + self.stop_voice_call_sender = self.start_voice_call(); + } else { + // The peer refused the voice call. + self.handler.on_voice_call_stop("Refused"); + } + } + } } _ => {} } diff --git a/src/flutter.rs b/src/flutter.rs index b4f1f6bc6..7062d85df 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -394,6 +394,22 @@ impl InvokeUiSession for FlutterHandler { fn switch_back(&self, peer_id: &str) { self.push_event("switch_back", [("peer_id", peer_id)].into()); } + + fn on_voice_call_start(&self) { + self.push_event("on_voice_call_start", [].into()); + } + + fn on_voice_call_stop(&self, reason: &str) { + self.push_event("on_voice_call_stop", [("reason", reason)].into()) + } + + fn on_voice_call_waiting(&self) { + self.push_event("on_voice_call_waiting", [].into()); + } + + fn on_voice_call_incoming(&self) { + self.push_event("on_voice_call_incoming", [].into()); + } } /// Create a new remote session with the given id. diff --git a/src/ipc.rs b/src/ipc.rs index d610fb84d..18f618847 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -210,7 +210,10 @@ pub enum Data { DataPortableService(DataPortableService), SwitchSidesRequest(String), SwitchSidesBack, - UrlLink(String) + UrlLink(String), + VoiceCallIncoming, + VoiceCallResponse(bool), + CloseVoiceCall(String), } #[tokio::main(flavor = "current_thread")] diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 65039f0fe..5a9abba9c 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -453,5 +453,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Voice call", "语音通话"), ("Text chat", "文字聊天"), ("Audio Transmission Mode", "音频传输模式"), + ("Refused", "已拒绝") ].iter().cloned().collect(); } diff --git a/src/server/connection.rs b/src/server/connection.rs index c3acae9cc..1007c71ca 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -5,7 +5,11 @@ use crate::clipboard_file::*; use crate::common::update_clipboard; #[cfg(windows)] use crate::portable_service::client as portable_client; -use crate::{video_service, client::{MediaSender, start_audio_thread, LatencyController, MediaData}, common::{get_default_sound_input, set_sound_input}}; +use crate::{ + client::{start_audio_thread, LatencyController, MediaData, MediaSender, new_voice_call_request, new_voice_call_response}, + common::{get_default_sound_input, set_sound_input}, + video_service, +}; #[cfg(any(target_os = "android", target_os = "ios"))] use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel}; use crate::{ipc, VERSION}; @@ -32,7 +36,10 @@ use serde_json::{json, value::Value}; use sha2::{Digest, Sha256}; #[cfg(not(any(target_os = "android", target_os = "ios")))] use std::sync::atomic::Ordering; -use std::sync::{atomic::AtomicI64, mpsc as std_mpsc}; +use std::{ + num::NonZeroI64, + sync::{atomic::AtomicI64, mpsc as std_mpsc}, +}; #[cfg(not(any(target_os = "android", target_os = "ios")))] use system_shutdown; @@ -90,13 +97,19 @@ pub struct Connection { recording: bool, last_test_delay: i64, lock_after_session_end: bool, - show_remote_cursor: bool, // by peer + show_remote_cursor: bool, + // by peer ip: String, - disable_clipboard: bool, // by peer - disable_audio: bool, // by peer - enable_file_transfer: bool, // by peer - audio_sender: MediaSender, // audio by the remote peer/client - tx_input: std_mpsc::Sender, // handle input messages + disable_clipboard: bool, + // by peer + disable_audio: bool, + // by peer + enable_file_transfer: bool, + // by peer + audio_sender: MediaSender, + // audio by the remote peer/client + tx_input: std_mpsc::Sender, + // handle input messages video_ack_required: bool, peer_info: (String, String), server_audit_conn: String, @@ -107,6 +120,8 @@ pub struct Connection { #[cfg(windows)] portable: PortableState, from_switch: bool, + voice_call_request_timestamp: Option, + audio_input_device_before_voice_call: Option, } impl ConnInner { @@ -216,6 +231,8 @@ impl Connection { portable: Default::default(), from_switch: false, audio_sender, + voice_call_request_timestamp: None, + audio_input_device_before_voice_call: None, }; #[cfg(not(any(target_os = "android", target_os = "ios")))] tokio::spawn(async move { @@ -380,6 +397,12 @@ impl Connection { msg.set_misc(misc); conn.send(msg).await; } + ipc::Data::VoiceCallResponse(accepted) => { + conn.start_voice_call().await; + } + ipc::Data::CloseVoiceCall(_reason) => { + conn.close_voice_call().await; + } _ => {} } }, @@ -650,15 +673,15 @@ impl Connection { .collect(); if !whitelist.is_empty() && whitelist - .iter() - .filter(|x| x == &"0.0.0.0") - .next() - .is_none() + .iter() + .filter(|x| x == &"0.0.0.0") + .next() + .is_none() && whitelist - .iter() - .filter(|x| IpCidr::from_str(x).map_or(false, |y| y.contains(addr.ip()))) - .next() - .is_none() + .iter() + .filter(|x| IpCidr::from_str(x).map_or(false, |y| y.contains(addr.ip()))) + .next() + .is_none() { self.send_login_error("Your ip is blocked by the peer") .await; @@ -784,7 +807,7 @@ impl Connection { }; self.post_conn_audit(json!({"peer": self.peer_info, "type": conn_type})); #[allow(unused_mut)] - let mut username = crate::platform::get_active_username(); + let mut username = crate::platform::get_active_username(); let mut res = LoginResponse::new(); let mut pi = PeerInfo { username: username.clone(), @@ -811,7 +834,7 @@ impl Connection { h265, ..Default::default() }) - .into(); + .into(); } if self.port_forward_socket.is_some() { @@ -855,7 +878,7 @@ impl Connection { privacy_mode: video_service::is_privacy_mode_supported(), ..Default::default() }) - .into(); + .into(); let mut sub_service = false; if self.file_transfer.is_some() { @@ -1138,7 +1161,7 @@ impl Connection { "Failed to access remote {}, please make sure if it is open", addr )) - .await; + .await; return false; } } @@ -1302,12 +1325,12 @@ impl Connection { } } Some(message::Union::Clipboard(cb)) => - { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if self.clipboard { - update_clipboard(cb, None); + { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if self.clipboard { + update_clipboard(cb, None); + } } - } Some(message::Union::Cliprdr(_clip)) => { if self.file_transfer_enabled() { #[cfg(windows)] @@ -1490,15 +1513,15 @@ impl Connection { } Some(misc::Union::RestartRemoteDevice(_)) => - { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if self.restart { - match system_shutdown::reboot() { - Ok(_) => log::info!("Restart by the peer"), - Err(e) => log::error!("Failed to restart:{}", e), + { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if self.restart { + match system_shutdown::reboot() { + Ok(_) => log::info!("Restart by the peer"), + Err(e) => log::error!("Failed to restart:{}", e), + } } } - } Some(misc::Union::ElevationRequest(r)) => match r.union { Some(elevation_request::Union::Direct(_)) => { #[cfg(windows)] @@ -1508,8 +1531,8 @@ impl Connection { err = portable_client::start_portable_service( portable_client::StartPara::Direct, ) - .err() - .map_or("".to_string(), |e| e.to_string()); + .err() + .map_or("".to_string(), |e| e.to_string()); } self.portable.elevation_requested = err.is_empty(); let mut misc = Misc::new(); @@ -1527,8 +1550,8 @@ impl Connection { err = portable_client::start_portable_service( portable_client::StartPara::Logon(_r.username, _r.password), ) - .err() - .map_or("".to_string(), |e| e.to_string()); + .err() + .map_or("".to_string(), |e| e.to_string()); } self.portable.elevation_requested = err.is_empty(); let mut misc = Misc::new(); @@ -1541,12 +1564,7 @@ impl Connection { _ => {} }, Some(misc::Union::AudioFormat(format)) => { - if !self.disable_audio { - // Switch to default input device - let default_sound_device = get_default_sound_input(); - if let Some(device) = default_sound_device { - set_sound_input(device); - } + if !self.disable_audio { allow_err!(self.audio_sender.send(MediaData::AudioFormat(format))); } } @@ -1559,7 +1577,7 @@ impl Connection { "--switch_uuid", uuid.to_string().as_ref(), ]) - .ok(); + .ok(); self.send_close_reason_no_retry("Closed as expected").await; self.on_close("switch sides", false).await; return false; @@ -1573,10 +1591,19 @@ impl Connection { } } Some(message::Union::VoiceCallRequest(request)) => { - // TODO + if request.is_connect { + self.voice_call_request_timestamp = Some( + NonZeroI64::new(request.req_timestamp) + .unwrap_or(NonZeroI64::new(get_time()).unwrap()), + ); + // Call cm. + self.send_to_cm(Data::VoiceCallIncoming); + } else { + self.close_voice_call().await; + } } - Some(message::Union::VoiceCallResponse(response)) => { - // TODO + Some(message::Union::VoiceCallResponse(_response)) => { + // TODO: Maybe we can do a voice call from cm directly. } _ => {} } @@ -1584,6 +1611,34 @@ impl Connection { true } + pub async fn start_voice_call(&self) { + if let Some(ts) = conn.voice_call_request_timestamp.take() { + let msg = new_voice_call_response(ts.get(), accepted); + conn.send(msg).await; + if accepted { + // Backup the default input device. + let audio_input_device = Config::get_option("audio-input"); + conn.audio_input_device_before_voice_call = Some(audio_input_device); + // Switch to default input device + let default_sound_device = get_default_sound_input(); + if let Some(device) = default_sound_device { + set_sound_input(device); + } + } + } else { + log::warn!("Possible a voice call attack."); + } + } + + pub async fn close_voice_call(&mut self) { + // Restore to the prior audio device. + if let Some(sound_input) = std::mem::replace(&mut self.audio_input_device_before_voice_call, None) { + set_sound_input(sound_input); + // Notify the connection manager. + self.send_to_cm(Data::CloseVoiceCall("Closed manually by the peer".to_owned())); + } + } + async fn update_option(&mut self, o: &OptionMessage) { log::info!("Option update: {:?}", o); if let Ok(q) = o.image_quality.enum_value() { @@ -1752,13 +1807,13 @@ impl Connection { lock_screen().await; } #[cfg(not(any(target_os = "android", target_os = "ios")))] - let data = if self.chat_unanswered { + let data = if self.chat_unanswered { ipc::Data::Disconnected } else { ipc::Data::Close }; #[cfg(any(target_os = "android", target_os = "ios"))] - let data = ipc::Data::Close; + let data = ipc::Data::Close; self.tx_to_cm.send(data).ok(); self.port_forward_socket.take(); } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 5d6692c3b..eb83890d4 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -266,6 +266,22 @@ impl InvokeUiSession for SciterHandler { } fn switch_back(&self, _id: &str) {} + + fn on_voice_call_start(&self) { + self.call("onVoiceCallStart", &make_args!()); + } + + fn on_voice_call_stop(&self, reason: &str) { + self.call("onVoiceCallStop", &make_args!(reason)); + } + + fn on_voice_call_waiting(&self) { + self.call("onVoiceCallWaiting", &make_args!()); + } + + fn on_voice_call_incoming(&self) { + self.call("onVoiceCallIncoming", &make_args!()); + } } pub struct SciterSession(Session); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 2f6827523..a740b373e 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -705,6 +705,10 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { fn clipboard(&self, content: String); fn cancel_msgbox(&self, tag: &str); fn switch_back(&self, id: &str); + fn on_voice_call_start(&self); + fn on_voice_call_stop(&self, reason: &str); + fn on_voice_call_waiting(&self); + fn on_voice_call_incoming(&self); } impl Deref for Session { From 11c60088111ba9d9312fd974896afee688a3a722 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 6 Feb 2023 11:53:37 +0800 Subject: [PATCH 069/117] fix: rust conn build --- src/server/connection.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index 1007c71ca..87b3f74ea 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -398,7 +398,9 @@ impl Connection { conn.send(msg).await; } ipc::Data::VoiceCallResponse(accepted) => { - conn.start_voice_call().await; + if accepted { + conn.start_voice_call().await; + } } ipc::Data::CloseVoiceCall(_reason) => { conn.close_voice_call().await; @@ -1611,19 +1613,17 @@ impl Connection { true } - pub async fn start_voice_call(&self) { - if let Some(ts) = conn.voice_call_request_timestamp.take() { + pub async fn start_voice_call(&mut self) { + if let Some(ts) = self.voice_call_request_timestamp.take() { let msg = new_voice_call_response(ts.get(), accepted); conn.send(msg).await; - if accepted { - // Backup the default input device. - let audio_input_device = Config::get_option("audio-input"); - conn.audio_input_device_before_voice_call = Some(audio_input_device); - // Switch to default input device - let default_sound_device = get_default_sound_input(); - if let Some(device) = default_sound_device { - set_sound_input(device); - } + // Backup the default input device. + let audio_input_device = Config::get_option("audio-input"); + self.audio_input_device_before_voice_call = Some(audio_input_device); + // Switch to default input device + let default_sound_device = get_default_sound_input(); + if let Some(device) = default_sound_device { + set_sound_input(device); } } else { log::warn!("Possible a voice call attack."); From a601e3b241eddc3f5a104fee89a8518be79ca34a Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 6 Feb 2023 12:10:15 +0800 Subject: [PATCH 070/117] fix: compile --- src/flutter_ffi.rs | 1 + src/server/connection.rs | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index e28332943..588733c37 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1288,6 +1288,7 @@ pub fn main_start_ipc_url_server() { /// Send a url scheme throught the ipc. /// /// * macOS only +#[allow(unused_variables)] pub fn send_url_scheme(url: String) { #[cfg(target_os = "macos")] thread::spawn(move || crate::ui::macos::handle_url_scheme(url)); diff --git a/src/server/connection.rs b/src/server/connection.rs index 87b3f74ea..c4c9ec168 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -398,9 +398,7 @@ impl Connection { conn.send(msg).await; } ipc::Data::VoiceCallResponse(accepted) => { - if accepted { - conn.start_voice_call().await; - } + conn.handle_voice_call(accepted).await; } ipc::Data::CloseVoiceCall(_reason) => { conn.close_voice_call().await; @@ -1613,17 +1611,19 @@ impl Connection { true } - pub async fn start_voice_call(&mut self) { + pub async fn handle_voice_call(&mut self, accepted: bool) { if let Some(ts) = self.voice_call_request_timestamp.take() { let msg = new_voice_call_response(ts.get(), accepted); - conn.send(msg).await; - // Backup the default input device. - let audio_input_device = Config::get_option("audio-input"); - self.audio_input_device_before_voice_call = Some(audio_input_device); - // Switch to default input device - let default_sound_device = get_default_sound_input(); - if let Some(device) = default_sound_device { - set_sound_input(device); + self.send(msg).await; + if accepted { + // Backup the default input device. + let audio_input_device = Config::get_option("audio-input"); + self.audio_input_device_before_voice_call = Some(audio_input_device); + // Switch to default input device + let default_sound_device = get_default_sound_input(); + if let Some(device) = default_sound_device { + set_sound_input(device); + } } } else { log::warn!("Possible a voice call attack."); From 850c4bcbbf5bfbf152ccda3e876330e5f7286f7e Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 6 Feb 2023 12:14:20 +0800 Subject: [PATCH 071/117] opt: uniform name --- src/client/io_loop.rs | 3 ++- src/flutter.rs | 4 ++-- src/ui/remote.rs | 4 ++-- src/ui_session_interface.rs | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 8f2b45321..e34df30b4 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -749,6 +749,7 @@ impl Remote { .unwrap_or(NonZeroI64::new(get_time()).unwrap()), ); allow_err!(peer.send(&msg).await); + self.handler.on_voice_call_waiting(); } Data::CloseVoiceCall => { self.stop_voice_call(); @@ -1262,7 +1263,7 @@ impl Remote { self.stop_voice_call_sender = self.start_voice_call(); } else { // The peer refused the voice call. - self.handler.on_voice_call_stop("Refused"); + self.handler.on_voice_call_closed("Refused"); } } } diff --git a/src/flutter.rs b/src/flutter.rs index 7062d85df..f8d8569ba 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -399,8 +399,8 @@ impl InvokeUiSession for FlutterHandler { self.push_event("on_voice_call_start", [].into()); } - fn on_voice_call_stop(&self, reason: &str) { - self.push_event("on_voice_call_stop", [("reason", reason)].into()) + fn on_voice_call_closed(&self, reason: &str) { + self.push_event("on_voice_call_closed", [("reason", reason)].into()) } fn on_voice_call_waiting(&self) { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index eb83890d4..9888e5831 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -271,8 +271,8 @@ impl InvokeUiSession for SciterHandler { self.call("onVoiceCallStart", &make_args!()); } - fn on_voice_call_stop(&self, reason: &str) { - self.call("onVoiceCallStop", &make_args!(reason)); + fn on_voice_call_closed(&self, reason: &str) { + self.call("onVoiceCallClosed", &make_args!(reason)); } fn on_voice_call_waiting(&self) { diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index a740b373e..4b47608f9 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -706,7 +706,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { fn cancel_msgbox(&self, tag: &str); fn switch_back(&self, id: &str); fn on_voice_call_start(&self); - fn on_voice_call_stop(&self, reason: &str); + fn on_voice_call_closed(&self, reason: &str); fn on_voice_call_waiting(&self); fn on_voice_call_incoming(&self); } From 040396b3f8421075adce6762010bd74b964d407f Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 6 Feb 2023 12:53:57 +0800 Subject: [PATCH 072/117] feat: cm interface --- src/flutter.rs | 12 ++++++++++++ src/ipc.rs | 1 + src/server/connection.rs | 1 + src/ui/cm.rs | 12 ++++++++++++ src/ui_cm_interface.rs | 27 +++++++++++++++++++++++++++ 5 files changed, 53 insertions(+) diff --git a/src/flutter.rs b/src/flutter.rs index f8d8569ba..e83beb03b 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -537,6 +537,18 @@ pub mod connection_manager { fn show_elevation(&self, show: bool) { self.push_event("show_elevation", vec![("show", &show.to_string())]); } + + fn voice_call_started(&self, id: i32) { + self.push_event("voice_call_started", vec![("show", &id.to_string())]); + } + + fn voice_call_incoming(&self, id: i32) { + self.push_event("voice_call_incoming", vec![("id", &id.to_string())]); + } + + fn voice_call_closed(&self, id: i32, reason: &str) { + self.push_event("voice_call_closed", vec![("id", &id.to_string()), ("reason", &reason.to_string())]); + } } impl FlutterHandler { diff --git a/src/ipc.rs b/src/ipc.rs index 18f618847..0ede560fc 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -212,6 +212,7 @@ pub enum Data { SwitchSidesBack, UrlLink(String), VoiceCallIncoming, + StartVoiceCall, VoiceCallResponse(bool), CloseVoiceCall(String), } diff --git a/src/server/connection.rs b/src/server/connection.rs index c4c9ec168..da0126213 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1624,6 +1624,7 @@ impl Connection { if let Some(device) = default_sound_device { set_sound_input(device); } + self.send_to_cm(Data::StartVoiceCall); } } else { log::warn!("Possible a voice call attack."); diff --git a/src/ui/cm.rs b/src/ui/cm.rs index 2bd8824db..dc941c3d0 100644 --- a/src/ui/cm.rs +++ b/src/ui/cm.rs @@ -55,6 +55,18 @@ impl InvokeUiCM for SciterHandler { fn show_elevation(&self, show: bool) { self.call("showElevation", &make_args!(show)); } + + fn voice_call_started(&self, id: i32) { + self.call("voice_call_started", &make_args!(id)); + } + + fn voice_call_incoming(&self, id: i32) { + self.call("voice_call_incoming", &make_args!(id)); + } + + fn voice_call_closed(&self, id: i32, reason: &str) { + self.call("voice_call_incoming", &make_args!(id, reason)); + } } impl SciterHandler { diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 5d451e4d4..1120a1731 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -88,6 +88,12 @@ pub trait InvokeUiCM: Send + Clone + 'static + Sized { fn change_language(&self); fn show_elevation(&self, show: bool); + + fn voice_call_started(&self, id: i32); + + fn voice_call_incoming(&self, id: i32); + + fn voice_call_closed(&self, id: i32, reason: &str); } impl Deref for ConnectionManager { @@ -180,6 +186,18 @@ impl ConnectionManager { fn show_elevation(&self, show: bool) { self.ui_handler.show_elevation(show); } + + fn voice_call_started(&self, id: i32) { + self.ui_handler.voice_call_started(id); + } + + fn voice_call_incoming(&self, id: i32) { + self.ui_handler.voice_call_incoming(id); + } + + fn voice_call_closed(&self, id: i32, reason: &str) { + self.ui_handler.voice_call_closed(id, reason); + } } #[inline] @@ -389,6 +407,15 @@ impl IpcTaskRunner { Data::DataPortableService(ipc::DataPortableService::CmShowElevation(show)) => { self.cm.show_elevation(show); } + Data::StartVoiceCall => { + self.cm.voice_call_started(self.conn_id); + } + Data::VoiceCallIncoming => { + self.cm.voice_call_incoming(self.conn_id); + } + Data::CloseVoiceCall(reason) => { + self.cm.voice_call_closed(self.conn_id, reason.as_str()); + } _ => { } From ea391542fcf607619631c63505df28fd84ec7c67 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 6 Feb 2023 15:36:36 +0800 Subject: [PATCH 073/117] opt: rename to on_voice_call_started --- src/client/io_loop.rs | 2 +- src/flutter.rs | 4 ++-- src/ui/remote.rs | 2 +- src/ui_session_interface.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index e34df30b4..d49227864 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1259,7 +1259,7 @@ impl Remote { } else { if response.accepted { // The peer accepts the voice call. - self.handler.on_voice_call_start(); + self.handler.on_voice_call_started(); self.stop_voice_call_sender = self.start_voice_call(); } else { // The peer refused the voice call. diff --git a/src/flutter.rs b/src/flutter.rs index e83beb03b..4249e4d94 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -395,8 +395,8 @@ impl InvokeUiSession for FlutterHandler { self.push_event("switch_back", [("peer_id", peer_id)].into()); } - fn on_voice_call_start(&self) { - self.push_event("on_voice_call_start", [].into()); + fn on_voice_call_started(&self) { + self.push_event("on_voice_call_started", [].into()); } fn on_voice_call_closed(&self, reason: &str) { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 9888e5831..999b409e0 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -267,7 +267,7 @@ impl InvokeUiSession for SciterHandler { fn switch_back(&self, _id: &str) {} - fn on_voice_call_start(&self) { + fn on_voice_call_started(&self) { self.call("onVoiceCallStart", &make_args!()); } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 4b47608f9..f63bbd081 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -705,7 +705,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { fn clipboard(&self, content: String); fn cancel_msgbox(&self, tag: &str); fn switch_back(&self, id: &str); - fn on_voice_call_start(&self); + fn on_voice_call_started(&self); fn on_voice_call_closed(&self, reason: &str); fn on_voice_call_waiting(&self); fn on_voice_call_incoming(&self); From 5e21a81a5cc6aca17ba9a4726a626b14b06a67cc Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 6 Feb 2023 20:10:39 +0800 Subject: [PATCH 074/117] wip: implement flutter ui --- Cargo.lock | 5 +-- Cargo.toml | 2 +- flutter/assets/voice_call.svg | 1 + flutter/assets/voice_call_waiting.svg | 1 + .../lib/desktop/widgets/remote_menubar.dart | 32 ++++++++++++++++++- flutter/lib/models/chat_model.dart | 32 +++++++++++++++++++ flutter/lib/models/model.dart | 15 +++++++++ 7 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 flutter/assets/voice_call.svg create mode 100644 flutter/assets/voice_call_waiting.svg diff --git a/Cargo.lock b/Cargo.lock index e15641363..52fcc76cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1334,8 +1334,9 @@ checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" [[package]] name = "default-net" -version = "0.11.0" -source = "git+https://github.com/Kingtous/default-net#bdaad8dd5b08efcba303e71729d3d0b1d5ccdb25" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e349ed1e06fb344a7dd8b5a676375cf671b31e8900075dd2be816efc063a63" dependencies = [ "libc", "memalloc", diff --git a/Cargo.toml b/Cargo.toml index 936b9e349..b315024e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ base64 = "0.13" sysinfo = "0.24" num_cpus = "1.13" bytes = { version = "1.2", features = ["serde"] } -default-net = { git = "https://github.com/Kingtous/default-net" } +default-net = "0.12.0" wol-rs = "0.9.1" flutter_rust_bridge = { version = "1.61.1", optional = true } errno = "0.2.8" diff --git a/flutter/assets/voice_call.svg b/flutter/assets/voice_call.svg new file mode 100644 index 000000000..0637b58d9 --- /dev/null +++ b/flutter/assets/voice_call.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/voice_call_waiting.svg b/flutter/assets/voice_call_waiting.svg new file mode 100644 index 000000000..fd8334f92 --- /dev/null +++ b/flutter/assets/voice_call_waiting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 0004c65fe..d06be52fa 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -426,6 +426,7 @@ class _RemoteMenubarState extends State { menubarItems.add(_buildKeyboard(context)); if (!isWeb) { menubarItems.add(_buildChat(context)); + menubarItems.add(_buildVoiceCall(context)); } menubarItems.add(_buildRecording(context)); menubarItems.add(_buildClose(context)); @@ -707,6 +708,32 @@ class _RemoteMenubarState extends State { ); } + Widget _buildVoiceCall(BuildContext context) { + return Obx( + () { + switch (widget.ffi.chatModel.voiceCallStatus.value) { + case VoiceCallStatus.waitingForResponse: + return SvgPicture.asset( + "assets/voice_call_waiting.svg", + color: _MenubarTheme.commonColor, + width: Theme.of(context).iconTheme.size ?? 24.0, + height: Theme.of(context).iconTheme.size ?? 24.0, + ); + break; + case VoiceCallStatus.connected: + return SvgPicture.asset( + "assets/voice_call.svg", + color: Colors.red, + width: Theme.of(context).iconTheme.size ?? 24.0, + height: Theme.of(context).iconTheme.size ?? 24.0, + ); + default: + return const Offstage(); + } + }, + ); + } + List> _getChatMenu(BuildContext context) { final List> chatMenu = []; const EdgeInsets padding = EdgeInsets.only(left: 14.0, right: 5.0); @@ -728,7 +755,10 @@ class _RemoteMenubarState extends State { translate('Voice call'), style: style, ), - proc: () {}, + proc: () { + // Request a voice call. + bind.sessionRequestVoiceCall(id: widget.id); + }, padding: padding, dismissOnClicked: true, ), diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index 18a0be279..61602c5b4 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -2,6 +2,7 @@ import 'package:dash_chat_2/dash_chat_2.dart'; import 'package:draggable_float_widget/draggable_float_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:get/get.dart'; import 'package:window_manager/window_manager.dart'; import '../consts.dart'; @@ -33,8 +34,13 @@ class ChatModel with ChangeNotifier { OverlayState? _overlayState; OverlayEntry? chatIconOverlayEntry; OverlayEntry? chatWindowOverlayEntry; + bool isConnManager = false; + final Rx _voiceCallStatus = Rx(VoiceCallStatus.notStarted); + + Rx get voiceCallStatus => _voiceCallStatus; + final ChatUser me = ChatUser( id: "", firstName: "Me", @@ -292,4 +298,30 @@ class ChatModel with ChangeNotifier { resetClientMode() { _messages[clientModeID]?.clear(); } + + void onVoiceCallWaiting() { + _voiceCallStatus.value = VoiceCallStatus.waitingForResponse; + } + + void onVoiceCallStarted() { + _voiceCallStatus.value = VoiceCallStatus.connected; + } + + void onVoiceCallClosed(String reason) { + _voiceCallStatus.value = VoiceCallStatus.notStarted; + } + + void onVoiceCallIncoming() { + if (isConnManager) { + _voiceCallStatus.value = VoiceCallStatus.incoming; + } + } } + +enum VoiceCallStatus { + notStarted, + waitingForResponse, + connected, + // Connection manager only. + incoming +} \ No newline at end of file diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index daf7bfe34..2a4c68839 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -203,6 +203,21 @@ class FfiModel with ChangeNotifier { } else if (name == "on_url_scheme_received") { final url = evt['url'].toString(); parseRustdeskUri(url); + } else if (name == "on_voice_call_waiting") { + // Waiting for the response from the peer. + parent.target?.chatModel.onVoiceCallWaiting(); + } else if (name == "on_voice_call_started") { + // Voice call is connected. + parent.target?.chatModel.onVoiceCallStarted(); + } else if (name == "on_voice_call_closed") { + // Voice call is closed with reason. + final reason = evt['reason'].toString(); + parent.target?.chatModel.onVoiceCallClosed(reason); + } else if (name == "on_voice_call_incoming") { + // Voice call is requested by the peer. + parent.target?.chatModel.onVoiceCallIncoming(); + } else { + debugPrint("Unknown event name: $name"); } }; } From 2943d2d0ccaad9ffe580b98979af95cf44100fb5 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 16:11:55 +0800 Subject: [PATCH 075/117] feat: cm interface --- flutter/lib/desktop/pages/server_page.dart | 42 +++++++++++++++++- .../lib/desktop/widgets/remote_menubar.dart | 32 +++++++++----- flutter/lib/models/chat_model.dart | 4 ++ flutter/lib/models/model.dart | 2 + flutter/lib/models/server_model.dart | 18 ++++++++ src/client/io_loop.rs | 1 + src/flutter.rs | 13 ++---- src/flutter_ffi.rs | 8 ++++ src/server/connection.rs | 2 +- src/ui/cm.rs | 19 ++++---- src/ui_cm_interface.rs | 44 +++++++++++++++---- src/ui_session_interface.rs | 2 +- 12 files changed, 143 insertions(+), 44 deletions(-) diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index 521413647..b2f70cdd5 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -521,6 +521,38 @@ class _CmControlPanel extends StatelessWidget { return Column( mainAxisAlignment: MainAxisAlignment.end, children: [ + Offstage( + offstage: !client.inVoiceCall, + child: buildButton(context, + color: Colors.purple, + onClick: () => closeVoiceCall(), + icon: Icon(Icons.reply, color: Colors.white), + text: "Stop voice call", + textColor: Colors.white), + ), + Offstage( + offstage: !client.incomingVoiceCall, + child: Row( + children: [ + Expanded( + child: buildButton(context, + color: MyTheme.accent, + onClick: () => handleVoiceCall(true), + icon: Icon(Icons.phone, color: Colors.white), + text: "Accept", + textColor: Colors.white), + ), + Expanded( + child: buildButton(context, + color: Colors.red, + onClick: () => handleVoiceCall(false), + icon: Icon(Icons.phone, color: Colors.white), + text: "Deny", + textColor: Colors.white), + ) + ], + ), + ), Offstage( offstage: !client.fromSwitch, child: buildButton(context, @@ -626,7 +658,7 @@ class _CmControlPanel extends StatelessWidget { .marginSymmetric(horizontal: showElevation ? 0 : bigMargin); } - buildButton( + Widget buildButton( BuildContext context, { required Color? color, required Function() onClick, @@ -692,6 +724,14 @@ class _CmControlPanel extends StatelessWidget { void handleSwitchBack(BuildContext context) { bind.cmSwitchBack(connId: client.id); } + + void handleVoiceCall(bool accept) { + bind.cmHandleIncomingVoiceCall(id: client.id, accept: accept); + } + + void closeVoiceCall() { + bind.cmCloseVoiceCall(id: client.id); + } } void checkClickTime(int id, Function() callback) async { diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index d06be52fa..653ff37b1 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -713,19 +713,27 @@ class _RemoteMenubarState extends State { () { switch (widget.ffi.chatModel.voiceCallStatus.value) { case VoiceCallStatus.waitingForResponse: - return SvgPicture.asset( - "assets/voice_call_waiting.svg", - color: _MenubarTheme.commonColor, - width: Theme.of(context).iconTheme.size ?? 24.0, - height: Theme.of(context).iconTheme.size ?? 24.0, - ); - break; + return IconButton( + onPressed: () { + widget.ffi.chatModel.closeVoiceCall(widget.id); + }, + icon: SvgPicture.asset( + "assets/voice_call_waiting.svg", + color: Colors.red, + width: Theme.of(context).iconTheme.size ?? 24.0, + height: Theme.of(context).iconTheme.size ?? 24.0, + )); case VoiceCallStatus.connected: - return SvgPicture.asset( - "assets/voice_call.svg", - color: Colors.red, - width: Theme.of(context).iconTheme.size ?? 24.0, - height: Theme.of(context).iconTheme.size ?? 24.0, + return IconButton( + onPressed: () { + widget.ffi.chatModel.closeVoiceCall(widget.id); + }, + icon: SvgPicture.asset( + "assets/voice_call.svg", + color: Colors.red, + width: Theme.of(context).iconTheme.size ?? 24.0, + height: Theme.of(context).iconTheme.size ?? 24.0, + ), ); default: return const Offstage(); diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index 61602c5b4..14af96570 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -316,6 +316,10 @@ class ChatModel with ChangeNotifier { _voiceCallStatus.value = VoiceCallStatus.incoming; } } + + void closeVoiceCall(String id) { + bind.sessionCloseVoiceCall(id: id); + } } enum VoiceCallStatus { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 2a4c68839..a2fe205af 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -216,6 +216,8 @@ class FfiModel with ChangeNotifier { } else if (name == "on_voice_call_incoming") { // Voice call is requested by the peer. parent.target?.chatModel.onVoiceCallIncoming(); + } else if (name == "update_voice_call_state") { + parent.target?.serverModel.updateVoiceCallState(evt); } else { debugPrint("Unknown event name: $name"); } diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 56dca4cdf..6cd905c37 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -579,6 +579,20 @@ class ServerModel with ChangeNotifier { notifyListeners(); } } + + void updateVoiceCallState(Map evt) { + try { + final client = Client.fromJson(jsonDecode(evt["client"])); + final index = _clients.indexWhere((element) => element.id == client.id); + if (index != -1) { + _clients[index].inVoiceCall = evt['in_voice_call']; + _clients[index].incomingVoiceCall = evt['incoming_voice_call']; + notifyListeners(); + } + } catch (e) { + debugPrint("updateVoiceCallState failed: $e"); + } + } } enum ClientType { @@ -602,6 +616,8 @@ class Client { bool recording = false; bool disconnected = false; bool fromSwitch = false; + bool inVoiceCall = false; + bool incomingVoiceCall = false; RxBool hasUnreadChatMessage = false.obs; @@ -623,6 +639,8 @@ class Client { recording = json['recording']; disconnected = json['disconnected']; fromSwitch = json['from_switch']; + inVoiceCall = json['in_voice_call']; + incomingVoiceCall = json['incoming_voice_call']; } Map toJson() { diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index d49227864..aa51df378 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -754,6 +754,7 @@ impl Remote { Data::CloseVoiceCall => { self.stop_voice_call(); let msg = new_voice_call_request(false); + self.handler.on_voice_call_closed("Closed manually by the peer"); allow_err!(peer.send(&msg).await); } _ => {} diff --git a/src/flutter.rs b/src/flutter.rs index 4249e4d94..a27a9d4e1 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -538,16 +538,9 @@ pub mod connection_manager { self.push_event("show_elevation", vec![("show", &show.to_string())]); } - fn voice_call_started(&self, id: i32) { - self.push_event("voice_call_started", vec![("show", &id.to_string())]); - } - - fn voice_call_incoming(&self, id: i32) { - self.push_event("voice_call_incoming", vec![("id", &id.to_string())]); - } - - fn voice_call_closed(&self, id: i32, reason: &str) { - self.push_event("voice_call_closed", vec![("id", &id.to_string()), ("reason", &reason.to_string())]); + fn update_voice_call_state(&self, client: &crate::ui_cm_interface::Client) { + let client_json = serde_json::to_string(&client).unwrap_or("".into()); + self.push_event("update_voice_call_state", vec![("client", &client_json)]); } } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 588733c37..cfca0e082 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -838,6 +838,14 @@ pub fn session_close_voice_call(id: String) { } } +pub fn cm_handle_incoming_voice_call(id: i32, accept: bool) { + crate::ui_cm_interface::handle_incoming_voice_call(id, accept); +} + +pub fn cm_close_voice_call(id: i32) { + crate::ui_cm_interface::close_voice_call(id); +} + pub fn main_get_last_remote_id() -> String { LocalConfig::get_remote_id() } diff --git a/src/server/connection.rs b/src/server/connection.rs index da0126213..1e88b9b05 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1636,7 +1636,7 @@ impl Connection { if let Some(sound_input) = std::mem::replace(&mut self.audio_input_device_before_voice_call, None) { set_sound_input(sound_input); // Notify the connection manager. - self.send_to_cm(Data::CloseVoiceCall("Closed manually by the peer".to_owned())); + self.send_to_cm(Data::CloseVoiceCall("".to_owned())); } } diff --git a/src/ui/cm.rs b/src/ui/cm.rs index dc941c3d0..cce553154 100644 --- a/src/ui/cm.rs +++ b/src/ui/cm.rs @@ -56,16 +56,15 @@ impl InvokeUiCM for SciterHandler { self.call("showElevation", &make_args!(show)); } - fn voice_call_started(&self, id: i32) { - self.call("voice_call_started", &make_args!(id)); - } - - fn voice_call_incoming(&self, id: i32) { - self.call("voice_call_incoming", &make_args!(id)); - } - - fn voice_call_closed(&self, id: i32, reason: &str) { - self.call("voice_call_incoming", &make_args!(id, reason)); + fn update_voice_call_state(&self, client: &crate::ui_cm_interface::Client) { + self.call( + "updateVoiceCallState", + &make_args!( + client.id, + client.in_voice_call, + client.incoming_voice_call + ), + ); } } diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 1120a1731..ccddab0ee 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -49,6 +49,8 @@ pub struct Client { pub restart: bool, pub recording: bool, pub from_switch: bool, + pub in_voice_call: bool, + pub incoming_voice_call: bool, #[serde(skip)] tx: UnboundedSender, } @@ -89,11 +91,7 @@ pub trait InvokeUiCM: Send + Clone + 'static + Sized { fn show_elevation(&self, show: bool); - fn voice_call_started(&self, id: i32); - - fn voice_call_incoming(&self, id: i32); - - fn voice_call_closed(&self, id: i32, reason: &str); + fn update_voice_call_state(&self, client: &Client); } impl Deref for ConnectionManager { @@ -144,6 +142,8 @@ impl ConnectionManager { recording, from_switch, tx, + in_voice_call: false, + incoming_voice_call: false }; CLIENTS .write() @@ -188,15 +188,27 @@ impl ConnectionManager { } fn voice_call_started(&self, id: i32) { - self.ui_handler.voice_call_started(id); + if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { + client.incoming_voice_call = false; + client.in_voice_call = true; + self.ui_handler.update_voice_call_state(client); + } } fn voice_call_incoming(&self, id: i32) { - self.ui_handler.voice_call_incoming(id); + if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { + client.incoming_voice_call = true; + client.in_voice_call = false; + self.ui_handler.update_voice_call_state(client); + } } - fn voice_call_closed(&self, id: i32, reason: &str) { - self.ui_handler.voice_call_closed(id, reason); + fn voice_call_closed(&self, id: i32, _reason: &str) { + if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { + client.incoming_voice_call = false; + client.in_voice_call = false; + self.ui_handler.update_voice_call_state(client); + } } } @@ -832,3 +844,17 @@ pub fn elevate_portable(_id: i32) { } } } + +#[inline] +pub fn handle_incoming_voice_call(id: i32, accept: bool) { + if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { + allow_err!(client.tx.send(Data::VoiceCallResponse(accept))); + }; +} + +#[inline] +pub fn close_voice_call(id: i32) { + if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { + allow_err!(client.tx.send(Data::CloseVoiceCall("".to_owned()))); + }; +} \ No newline at end of file diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index f63bbd081..cd0bdcde2 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -661,7 +661,7 @@ impl Session { pub fn request_voice_call(&self) { self.send(Data::NewVoiceCall); } - + pub fn close_voice_call(&self) { self.send(Data::CloseVoiceCall); } From bd07f60a1109aff1ef0aa87b8621b2d80ee326b6 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 7 Feb 2023 16:41:33 +0800 Subject: [PATCH 076/117] disable blank issue, use better format --- .github/ISSUE_TEMPLATE/bug_report.md | 32 -------------- .github/ISSUE_TEMPLATE/bug_report.yaml | 49 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 1 - .github/ISSUE_TEMPLATE/feature_request.md | 10 ----- .github/ISSUE_TEMPLATE/feature_request.yaml | 25 +++++++++++ 5 files changed, 74 insertions(+), 43 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 5ba29c8b6..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Bug Report -about: Report a bug (English only, Please). -title: "" -labels: bug -assignees: '' - ---- - - - -**Describe the bug you encountered:** - -... - -**What did you expect to happen instead?** - -... - - -**How did you install `RustDesk`?** - - - ---- - -**RustDesk version and environment** - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 000000000..87fc6a5f5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,49 @@ +name: Bug Report +description: Create a bug report to help us improve +title: "[Bug] " +body: + - type: textarea + id: desc + attributes: + label: Bug Description + description: A clear and concise description of what the bug is + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: How to Reproduce + description: What steps can we take to reproduce this behavior? + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: A clear and concise description of what you expected to happen + validations: + required: true + - type: input + id: os + attributes: + label: Operating System + description: What OS are you seeing this bug on? local side / remote side. + validations: + required: true + - type: input + id: version + attributes: + label: RustDesk Version(s) + description: What version(s) of RustDesk do you see this bug on? local side / remote side. + validations: + required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If applicable, please add screenshots to help explain your problem + - type: textarea + id: context + attributes: + label: Additional Context + description: Add any additonal context about the problem here diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 01de3b330..7b43e397b 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,3 @@ -blank_issues_enabled: true contact_links: - name: Ask a question url: https://github.com/rustdesk/rustdesk/discussions/category_choices diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 0d21f017d..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Feature Request -about: Suggest an idea for this project ((English only, Please). -title: '' -labels: enhancement -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 000000000..01f6c6aca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,25 @@ +name: Feature Request +description: Suggest an idea for RustDesk +title: "[FR] " +body: + - type: textarea + id: desc + attributes: + label: Description + description: Describe your suggested feature and the main use cases + validations: + required: true + + - type: textarea + id: users + attributes: + label: Impact + description: What types of users can benefit from using the suggested feature? + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional Context + description: Add any additonal context about the feature here From fc933ad7b4c8e88f035aea44694ff53721895a33 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 16:47:19 +0800 Subject: [PATCH 077/117] fix: voice call 1 --- flutter/lib/models/server_model.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 6cd905c37..eec424bfe 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -585,8 +585,8 @@ class ServerModel with ChangeNotifier { final client = Client.fromJson(jsonDecode(evt["client"])); final index = _clients.indexWhere((element) => element.id == client.id); if (index != -1) { - _clients[index].inVoiceCall = evt['in_voice_call']; - _clients[index].incomingVoiceCall = evt['incoming_voice_call']; + _clients[index].inVoiceCall = client.inVoiceCall; + _clients[index].incomingVoiceCall = client.incomingVoiceCall; notifyListeners(); } } catch (e) { From cd6cdbff8f9c9fb38d7ad9631634ba2b9bea328d Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 16:53:46 +0800 Subject: [PATCH 078/117] fix: close notify --- src/client/io_loop.rs | 2 +- src/server/connection.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index aa51df378..234f4f842 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1264,7 +1264,7 @@ impl Remote { self.stop_voice_call_sender = self.start_voice_call(); } else { // The peer refused the voice call. - self.handler.on_voice_call_closed("Refused"); + self.handler.on_voice_call_closed(""); } } } diff --git a/src/server/connection.rs b/src/server/connection.rs index 1e88b9b05..7a16df811 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1614,7 +1614,6 @@ impl Connection { pub async fn handle_voice_call(&mut self, accepted: bool) { if let Some(ts) = self.voice_call_request_timestamp.take() { let msg = new_voice_call_response(ts.get(), accepted); - self.send(msg).await; if accepted { // Backup the default input device. let audio_input_device = Config::get_option("audio-input"); @@ -1625,7 +1624,10 @@ impl Connection { set_sound_input(device); } self.send_to_cm(Data::StartVoiceCall); + } else { + self.send_to_cm(Data::CloseVoiceCall("".to_owned())); } + self.send(msg).await; } else { log::warn!("Possible a voice call attack."); } From b82df0913731e60e87be25149c238ba5bb0c3e67 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 7 Feb 2023 17:00:36 +0800 Subject: [PATCH 079/117] new SECURITY.md --- docs/SECURITY.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/SECURITY.md diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 000000000..c595885f2 --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +## Reporting a Vulnerability + +We value security for the project very highly. We encourage all users to report any vulnerabilities they discover to us. +If you find a security vulnerability in the RustDesk project, please report it responsibly by sending an email to info@rustdesk.com. + +At this juncture, we don't have a bug bounty program. We are a small team trying to solve a big problem. We urge you to report any vulnerabilities responsibly +so that we can continue building a secure application for the entire community. From 66aaf243cf7654c40628187a0249ac77b9452c7a Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 17:09:36 +0800 Subject: [PATCH 080/117] opt: notify cm --- flutter/lib/desktop/pages/server_page.dart | 7 ++++--- flutter/lib/models/server_model.dart | 6 ++++++ src/client/io_loop.rs | 2 +- src/server/connection.rs | 7 ++++--- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index b2f70cdd5..a253b9aa2 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -524,7 +524,7 @@ class _CmControlPanel extends StatelessWidget { Offstage( offstage: !client.inVoiceCall, child: buildButton(context, - color: Colors.purple, + color: Colors.red, onClick: () => closeVoiceCall(), icon: Icon(Icons.reply, color: Colors.white), text: "Stop voice call", @@ -538,7 +538,7 @@ class _CmControlPanel extends StatelessWidget { child: buildButton(context, color: MyTheme.accent, onClick: () => handleVoiceCall(true), - icon: Icon(Icons.phone, color: Colors.white), + icon: Icon(Icons.phone_enabled, color: Colors.white), text: "Accept", textColor: Colors.white), ), @@ -546,7 +546,8 @@ class _CmControlPanel extends StatelessWidget { child: buildButton(context, color: Colors.red, onClick: () => handleVoiceCall(false), - icon: Icon(Icons.phone, color: Colors.white), + icon: + Icon(Icons.phone_disabled_rounded, color: Colors.white), text: "Deny", textColor: Colors.white), ) diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index eec424bfe..aab12ab5d 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -587,6 +587,12 @@ class ServerModel with ChangeNotifier { if (index != -1) { _clients[index].inVoiceCall = client.inVoiceCall; _clients[index].incomingVoiceCall = client.incomingVoiceCall; + if (client.incomingVoiceCall) { + // Has incoming phone call, let's set the window on top. + Future.delayed(Duration.zero, () { + window_on_top(null); + }); + } notifyListeners(); } } catch (e) { diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 234f4f842..05eab692a 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1259,7 +1259,7 @@ impl Remote { log::debug!("Possible encountering a voice call attack."); } else { if response.accepted { - // The peer accepts the voice call. + // The peer accepted the voice call. self.handler.on_voice_call_started(); self.stop_voice_call_sender = self.start_voice_call(); } else { diff --git a/src/server/connection.rs b/src/server/connection.rs index 7a16df811..86d837619 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1596,9 +1596,11 @@ impl Connection { NonZeroI64::new(request.req_timestamp) .unwrap_or(NonZeroI64::new(get_time()).unwrap()), ); - // Call cm. + // Notify the connection manager. self.send_to_cm(Data::VoiceCallIncoming); } else { + // Notify the connection manager. + self.send_to_cm(Data::CloseVoiceCall("".to_owned())); self.close_voice_call().await; } } @@ -1617,6 +1619,7 @@ impl Connection { if accepted { // Backup the default input device. let audio_input_device = Config::get_option("audio-input"); + log::debug!("Backup the sound input device {}", audio_input_device); self.audio_input_device_before_voice_call = Some(audio_input_device); // Switch to default input device let default_sound_device = get_default_sound_input(); @@ -1637,8 +1640,6 @@ impl Connection { // Restore to the prior audio device. if let Some(sound_input) = std::mem::replace(&mut self.audio_input_device_before_voice_call, None) { set_sound_input(sound_input); - // Notify the connection manager. - self.send_to_cm(Data::CloseVoiceCall("".to_owned())); } } From bdbb9ac2887e7af7785c3718f79e86a792058056 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 7 Feb 2023 17:15:01 +0800 Subject: [PATCH 081/117] opt: issues template --- .github/ISSUE_TEMPLATE/bug_report.yaml | 22 ++++++++++++---------- .github/ISSUE_TEMPLATE/task.yaml | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/task.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 87fc6a5f5..d3036ba24 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -1,7 +1,14 @@ name: Bug Report -description: Create a bug report to help us improve +description: Thanks for taking the time to fill out this bug report! Please fill the form in **English** title: "[Bug] " body: + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue related to this already exists. + options: + - label: I have searched the existing issues + required: true - type: textarea id: desc attributes: @@ -23,18 +30,13 @@ body: description: A clear and concise description of what you expected to happen validations: required: true - - type: input - id: os - attributes: - label: Operating System - description: What OS are you seeing this bug on? local side / remote side. - validations: - required: true - type: input id: version attributes: - label: RustDesk Version(s) - description: What version(s) of RustDesk do you see this bug on? local side / remote side. + label: Operating System(s) and RustDesk Version(s) on local side and remote side + description: What Operatiing System(s) and version(s) of RustDesk do you see this bug on? local side / remote side. + placeholder: | + Windows 10, 1.1.9 / osx 13.1, 1.1.8 validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/task.yaml b/.github/ISSUE_TEMPLATE/task.yaml new file mode 100644 index 000000000..a1ff080c5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/task.yaml @@ -0,0 +1,20 @@ +name: 📝 Task +description: Create a task for the team to work on +title: "[Task]: " +labels: [Task] +body: +- type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue related to this already exists. + options: + - label: I have searched the existing issues + required: true +- type: textarea + attributes: + label: SubTasks + placeholder: | + - Sub Task 1 + - Sub Task 2 + validations: + required: false From 29b1d106aa8385b03a40ecfa7e125831a3920caf Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 17:16:06 +0800 Subject: [PATCH 082/117] opt: ui and message --- flutter/lib/desktop/pages/server_page.dart | 4 ++-- src/lang/ca.rs | 8 +++----- src/lang/cn.rs | 7 +------ src/lang/cs.rs | 8 +++----- src/lang/da.rs | 6 +++--- src/lang/de.rs | 8 +++----- src/lang/eo.rs | 8 +++----- src/lang/es.rs | 9 ++++----- src/lang/fa.rs | 8 +++----- src/lang/fr.rs | 8 +++----- src/lang/gr.rs | 8 +++----- src/lang/hu.rs | 8 +++----- src/lang/id.rs | 8 +++----- src/lang/it.rs | 8 +++----- src/lang/ja.rs | 8 +++----- src/lang/ko.rs | 8 +++----- src/lang/kz.rs | 8 +++----- src/lang/pl.rs | 8 +++----- src/lang/pt_PT.rs | 8 +++----- src/lang/ptbr.rs | 8 +++----- src/lang/ro.rs | 8 +++----- src/lang/ru.rs | 12 +++++------- src/lang/sk.rs | 8 +++----- src/lang/sl.rs | 6 +++--- src/lang/sq.rs | 8 +++----- src/lang/sr.rs | 8 +++----- src/lang/sv.rs | 8 +++----- src/lang/template.rs | 8 +++----- src/lang/th.rs | 8 +++----- src/lang/tr.rs | 8 +++----- src/lang/tw.rs | 8 +++----- src/lang/ua.rs | 8 +++----- src/lang/vn.rs | 8 +++----- 33 files changed, 99 insertions(+), 161 deletions(-) diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index a253b9aa2..66a043fef 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -526,7 +526,7 @@ class _CmControlPanel extends StatelessWidget { child: buildButton(context, color: Colors.red, onClick: () => closeVoiceCall(), - icon: Icon(Icons.reply, color: Colors.white), + icon: Icon(Icons.phone_disabled_rounded, color: Colors.white), text: "Stop voice call", textColor: Colors.white), ), @@ -548,7 +548,7 @@ class _CmControlPanel extends StatelessWidget { onClick: () => handleVoiceCall(false), icon: Icon(Icons.phone_disabled_rounded, color: Colors.white), - text: "Deny", + text: "Dismiss", textColor: Colors.white), ) ], diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 4404e178d..e98c6636a 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 5a9abba9c..64c37709a 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -446,13 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "帧率"), ("Auto", "自动"), ("Other Default Options", "其它默认选项"), - ("Guest to Host", "被控到主机"), - ("Dual way", "双向"), - ("Guest to host audio transmission", "被控到主机音频传输"), - ("Dual-way audio transmission", "双向音频传输"), ("Voice call", "语音通话"), ("Text chat", "文字聊天"), - ("Audio Transmission Mode", "音频传输模式"), - ("Refused", "已拒绝") + ("Stop voice call", "停止语音聊天"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index a2a19a37a..70a3eb6c7 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 905f4814e..ae943e1e8 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -437,9 +437,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Switch Sides", ""), ("Please confirm if you want to share your desktop?", ""), ("Closed as expected", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), ("Display", ""), ("Default View Style", ""), ("Default Scroll Style", ""), @@ -449,5 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 4028e3337..44bbafdac 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "fps"), ("Auto", "Automatisch"), ("Other Default Options", "Weitere Standardoptionen"), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index fe3830b99..f457833f8 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index b9b31f109..220447454 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -436,7 +436,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Strong", "Fuerte"), ("Switch Sides", "Intercambiar lados"), ("Please confirm if you want to share your desktop?", "Por favor, confirma si quieres compartir tu escritorio"), - ("Closed as expected", "Cerrado como se esperaba"), + ("Closed as expected", ""), ("Display", "Pantalla"), ("Default View Style", "Estilo de vista predeterminado"), ("Default Scroll Style", "Estilo de desplazamiento predeterminado"), @@ -446,9 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", "Otras opciones predeterminadas"), - ("Closed as expected", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 0b92c6658..c206f91ff 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "خودکار"), ("Other Default Options", "سایر گزینه های پیش فرض"), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 4965f6dab..39ee3bc7f 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "Auto"), ("Other Default Options", "Autres options par défaut"), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/gr.rs b/src/lang/gr.rs index e40151ccf..7cb678ecc 100644 --- a/src/lang/gr.rs +++ b/src/lang/gr.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 0e1887e48..25562f556 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 689ae98cf..68a80e540 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 65f91ecec..9730bbc2d 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "Auto"), ("Other Default Options", "Altre Opzioni Predefinite"), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 33fb2da05..7069c0daf 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index c874dd695..43eb552d3 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 01014bab0..49c7b9916 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 9dd005bdd..41239961a 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "Auto"), ("Other Default Options", "Inne opcje domyślne"), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 716d3df82..e69a140c9 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index c7d0cd6ec..0887a5915 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 2d48b91b4..304353d42 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 8224cd5eb..1e6c6962a 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -435,8 +435,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Medium", "Средний"), ("Strong", "Стойкий"), ("Switch Sides", "Переключить стороны"), - ("Please confirm if you want to share your desktop?", "Подтверждаете, что хотите поделиться своим рабочим столом?"), - ("Closed as expected", "Закрыто по ожиданию"), + ("Please confirm if you want to share your desktop?", "Подтвердите, что хотите поделиться своим рабочим столом?"), + ("Closed as expected", ""), ("Display", "Отображение"), ("Default View Style", "Стиль отображения по умолчанию"), ("Default Scroll Style", "Стиль прокрутки по умолчанию"), @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "Авто"), ("Other Default Options", "Другие параметры по умолчанию"), - ("Please confirm if you want to share your desktop?", "Подтвердите, что хотите поделиться своим рабочим столом?"), - ("Closed as expected", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 5e0330954..6f6f7a18e 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index a75da46bd..2fb74fa5d 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -446,8 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index d3964a2e9..5d4a6e1ad 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 78059645d..31a3ade8f 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index ca2257756..e30c09e44 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 4355d643a..b88618074 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 57dfe6e43..1c75aaae7 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 49a42af4a..a9e2c1715 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 50e684258..7c49a29a2 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "幀率"), ("Auto", "自動"), ("Other Default Options", "其它默認選項"), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index f37ed341e..92c99d90c 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 5788a7f3d..8bb1d45e9 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -446,10 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", ""), ("Auto", ""), ("Other Default Options", ""), - ("Guest to Host", ""), - ("Dual way", ""), - ("Guest to host audio transmission", ""), - ("Dual way audio transmission", ""), - ("Audio Transmission Mode", ""), + ("Voice call", ""), + ("Text chat", ""), + ("Stop voice call", ""), ].iter().cloned().collect(); } From 926afc908fb00fc626a9a3012777bd73a3a5c1b2 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:20:53 +0800 Subject: [PATCH 083/117] Update bug_report.yaml --- .github/ISSUE_TEMPLATE/bug_report.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index d3036ba24..9bf1f6153 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -2,13 +2,13 @@ name: Bug Report description: Thanks for taking the time to fill out this bug report! Please fill the form in **English** title: "[Bug] " body: - - type: checkboxes - attributes: - label: Is there an existing issue for this? - description: Please search to see if an issue related to this already exists. - options: - - label: I have searched the existing issues - required: true + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue related to this already exists. + options: + - label: I have searched the existing issues + required: true - type: textarea id: desc attributes: From 9d0e4bdad0d81d2827d1dd0f506df2285e566791 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:22:23 +0800 Subject: [PATCH 084/117] Update feature_request.yaml --- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 01f6c6aca..ab4e9ae39 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -1,4 +1,4 @@ -name: Feature Request +name: 🛠️ Feature request description: Suggest an idea for RustDesk title: "[FR] " body: From f9864c1d0f77a3a9824d53934715eeca6bb4fb47 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:23:09 +0800 Subject: [PATCH 085/117] Update bug_report.yaml --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 9bf1f6153..16509a3be 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -1,4 +1,4 @@ -name: Bug Report +name: 🐞 Bug report description: Thanks for taking the time to fill out this bug report! Please fill the form in **English** title: "[Bug] " body: From 79ca1aa116e2d53c24bfa67f402c18e1cb4ea827 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:30:53 +0800 Subject: [PATCH 086/117] Update feature_request.yaml --- .github/ISSUE_TEMPLATE/feature_request.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index ab4e9ae39..50cd6d0cf 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -2,6 +2,14 @@ name: 🛠️ Feature request description: Suggest an idea for RustDesk title: "[FR] " body: + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue related to this already exists. + options: + - label: I have searched the existing issues + required: true + - type: textarea id: desc attributes: From 4ea41b52d3066031f8ea8ac32942c7e67f36eada Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 18:01:54 +0800 Subject: [PATCH 087/117] fix: execution order of listening ipc thread --- flutter/lib/main.dart | 3 +++ src/client.rs | 2 -- src/client/io_loop.rs | 2 +- src/core_main.rs | 2 -- src/flutter_ffi.rs | 4 ++++ 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index c19adf753..b923a31e1 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -114,6 +114,9 @@ Future initEnv(String appType) async { _registerEventHandler(); // Update the system theme. updateSystemWindowTheme(); + if (appType == kAppTypeConnectionManager) { + await bind.cmStartListenIpcThread(); + } } void runMainApp(bool startService) async { diff --git a/src/client.rs b/src/client.rs index 2ea33b655..020bea1f0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -773,7 +773,6 @@ impl AudioHandler { unsafe { std::slice::from_raw_parts::(buffer.as_ptr() as _, n * 4) }; self.simple.as_mut().map(|x| x.write(data_u8)); } - log::debug!("write Audio frame {} to system.", frame.timestamp); } }); } @@ -1595,7 +1594,6 @@ pub fn start_audio_thread( if let Ok(data) = audio_receiver.recv() { match data { MediaData::AudioFrame(af) => { - log::debug!("recved audio frame={}", af.timestamp); audio_handler.handle_frame(af); } MediaData::AudioFormat(f) => { diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 05eab692a..c8a0f2ca3 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -300,7 +300,7 @@ impl Remote { // check if client is closed match rx.try_recv() { Ok(_) | Err(std::sync::mpsc::TryRecvError::Disconnected) => { - log::debug!("Exit local audio service of client"); + log::debug!("Exit voice call audio service of client"); // unsubscribe CLIENT_SERVER.write().unwrap().subscribe( audio_service::NAME, diff --git a/src/core_main.rs b/src/core_main.rs index 99d0e888e..03d057eff 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -246,8 +246,6 @@ pub fn core_main() -> Option> { } else if args[0] == "--cm" { // call connection manager to establish connections // meanwhile, return true to call flutter window to show control panel - #[cfg(feature = "flutter")] - crate::flutter::connection_manager::start_listen_ipc_thread(); crate::ui_interface::start_option_status_sync(); } } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index cfca0e082..84407cd96 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1284,6 +1284,10 @@ pub fn main_hide_docker() -> SyncReturn { SyncReturn(true) } +pub fn cm_start_listen_ipc_thread() { + crate::flutter::connection_manager::start_listen_ipc_thread(); +} + /// Start an ipc server for receiving the url scheme. /// /// * Should only be called in the main flutter window. From 795b0068d0deefa1eeb99a52c8b6cef1fd1e30d5 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 18:17:31 +0800 Subject: [PATCH 088/117] opt: close voice call msg --- src/server/connection.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index 86d837619..1bacad124 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1599,8 +1599,6 @@ impl Connection { // Notify the connection manager. self.send_to_cm(Data::VoiceCallIncoming); } else { - // Notify the connection manager. - self.send_to_cm(Data::CloseVoiceCall("".to_owned())); self.close_voice_call().await; } } @@ -1641,6 +1639,7 @@ impl Connection { if let Some(sound_input) = std::mem::replace(&mut self.audio_input_device_before_voice_call, None) { set_sound_input(sound_input); } + self.send_to_cm(Data::CloseVoiceCall("".to_owned())); } async fn update_option(&mut self, o: &OptionMessage) { From db8b6d618f0d6b93b69f97dcfc42bca26063b2cf Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 19:09:22 +0800 Subject: [PATCH 089/117] fix: audio close status sync --- src/client/io_loop.rs | 11 +++++++++-- src/server/connection.rs | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index c8a0f2ca3..96ddd51f0 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1249,8 +1249,15 @@ impl Remote { self.handler .msgbox(&msgbox.msgtype, &msgbox.title, &msgbox.text, &link); } - Some(message::Union::VoiceCallRequest(_request)) => { - // TODO: maybe we will do voice call from the peer. + Some(message::Union::VoiceCallRequest(request)) => { + if request.is_connect { + // TODO: maybe we will do voice call from the peer in the future. + } else { + if let Some(sender) = self.stop_voice_call_sender.take() { + allow_err!(sender.send(())); + self.handler.on_voice_call_closed(""); + } + } } Some(message::Union::VoiceCallResponse(response)) => { let ts = std::mem::replace(&mut self.voice_call_request_timestamp, None); diff --git a/src/server/connection.rs b/src/server/connection.rs index 1bacad124..17417cf61 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -402,6 +402,9 @@ impl Connection { } ipc::Data::CloseVoiceCall(_reason) => { conn.close_voice_call().await; + // Notify the peer that we closed the voice call. + let req = new_voice_call_request(false); + conn.send(req).await; } _ => {} } @@ -1639,6 +1642,7 @@ impl Connection { if let Some(sound_input) = std::mem::replace(&mut self.audio_input_device_before_voice_call, None) { set_sound_input(sound_input); } + // Notify the connection manager that the voice call has been closed. self.send_to_cm(Data::CloseVoiceCall("".to_owned())); } From c4b1c51e9e745f32037e04c3ae17fd4a6f0799a5 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 19:33:58 +0800 Subject: [PATCH 090/117] opt: more debug info --- flutter/lib/main.dart | 4 +--- src/flutter.rs | 4 +++- src/server/connection.rs | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index b923a31e1..c61287d4f 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -114,9 +114,6 @@ Future initEnv(String appType) async { _registerEventHandler(); // Update the system theme. updateSystemWindowTheme(); - if (appType == kAppTypeConnectionManager) { - await bind.cmStartListenIpcThread(); - } } void runMainApp(bool startService) async { @@ -219,6 +216,7 @@ void runMultiWindow( void runConnectionManagerScreen(bool hide) async { await initEnv(kAppTypeConnectionManager); + await bind.cmStartListenIpcThread(); _runApp( '', const DesktopServerPage(), diff --git a/src/flutter.rs b/src/flutter.rs index a27a9d4e1..2d7d3fb86 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -549,9 +549,11 @@ pub mod connection_manager { let mut h: HashMap<&str, &str> = event.iter().cloned().collect(); assert!(h.get("name").is_none()); h.insert("name", name); - + if let Some(s) = GLOBAL_EVENT_STREAM.read().unwrap().get(super::APP_TYPE_CM) { s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned())); + } else { + println!("Push event {} failed. No {} event stream found.", name, super::APP_TYPE_CM); }; } } diff --git a/src/server/connection.rs b/src/server/connection.rs index 17417cf61..a8849b4e6 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -401,10 +401,11 @@ impl Connection { conn.handle_voice_call(accepted).await; } ipc::Data::CloseVoiceCall(_reason) => { + log::debug!("Close the voice call from the ipc."); conn.close_voice_call().await; // Notify the peer that we closed the voice call. - let req = new_voice_call_request(false); - conn.send(req).await; + let msg = new_voice_call_request(false); + conn.send(msg).await; } _ => {} } From 86b88c2927a0251dcd8cdbd90e799ced45bb5d04 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 19:40:50 +0800 Subject: [PATCH 091/117] opt: open audio when needed --- src/client/io_loop.rs | 3 ++- src/server/connection.rs | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 96ddd51f0..f5792bce3 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1251,8 +1251,9 @@ impl Remote { } Some(message::Union::VoiceCallRequest(request)) => { if request.is_connect { - // TODO: maybe we will do voice call from the peer in the future. + // TODO: maybe we will do a voice call from the peer in the future. } else { + log::debug!("The remote has requested to close the voice call"); if let Some(sender) = self.stop_voice_call_sender.take() { allow_err!(sender.send(())); self.handler.on_voice_call_closed(""); diff --git a/src/server/connection.rs b/src/server/connection.rs index a8849b4e6..02888d1ea 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -106,7 +106,7 @@ pub struct Connection { // by peer enable_file_transfer: bool, // by peer - audio_sender: MediaSender, + audio_sender: Option, // audio by the remote peer/client tx_input: std_mpsc::Sender, // handle input messages @@ -184,11 +184,6 @@ impl Connection { let mut hbbs_rx = crate::hbbs_http::sync::signal_receiver(); let tx_cloned = tx.clone(); - // Start a audio thread to play the audio sent by peer. - let latency_controller = LatencyController::new(); - // No video frame will be sent here, so we need to disable latency controller, or audio check may fail. - latency_controller.lock().unwrap().set_audio_only(true); - let audio_sender = start_audio_thread(Some(latency_controller)); let mut conn = Self { inner: ConnInner { id, @@ -230,7 +225,7 @@ impl Connection { #[cfg(windows)] portable: Default::default(), from_switch: false, - audio_sender, + audio_sender: None, voice_call_request_timestamp: None, audio_input_device_before_voice_call: None, }; @@ -1569,7 +1564,14 @@ impl Connection { }, Some(misc::Union::AudioFormat(format)) => { if !self.disable_audio { - allow_err!(self.audio_sender.send(MediaData::AudioFormat(format))); + // Drop the audio sender previously. + std::mem::replace(&mut self.audio_sender, None); + // Start a audio thread to play the audio sent by peer. + let latency_controller = LatencyController::new(); + // No video frame will be sent here, so we need to disable latency controller, or audio check may fail. + latency_controller.lock().unwrap().set_audio_only(true); + self.audio_sender = Some(start_audio_thread(Some(latency_controller))); + allow_err!(self.audio_sender.unwrap().send(MediaData::AudioFormat(format))); } } #[cfg(feature = "flutter")] From 404915c97512cbb9a60d58f70ae9eb83c60c2733 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 19:49:42 +0800 Subject: [PATCH 092/117] fix: compile --- src/server/connection.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index 02888d1ea..9ce53c960 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1565,13 +1565,13 @@ impl Connection { Some(misc::Union::AudioFormat(format)) => { if !self.disable_audio { // Drop the audio sender previously. - std::mem::replace(&mut self.audio_sender, None); + drop(std::mem::replace(&mut self.audio_sender, None)); // Start a audio thread to play the audio sent by peer. let latency_controller = LatencyController::new(); // No video frame will be sent here, so we need to disable latency controller, or audio check may fail. latency_controller.lock().unwrap().set_audio_only(true); self.audio_sender = Some(start_audio_thread(Some(latency_controller))); - allow_err!(self.audio_sender.unwrap().send(MediaData::AudioFormat(format))); + allow_err!(self.audio_sender.as_ref().unwrap().send(MediaData::AudioFormat(format))); } } #[cfg(feature = "flutter")] @@ -1593,7 +1593,11 @@ impl Connection { }, Some(message::Union::AudioFrame(frame)) => { if !self.disable_audio { - allow_err!(self.audio_sender.send(MediaData::AudioFrame(frame))); + if let Some(sender) = &self.audio_sender { + allow_err!(sender.send(MediaData::AudioFrame(frame))); + } else { + log::warn!("Processing audio frame without the voice call audio sender."); + } } } Some(message::Union::VoiceCallRequest(request)) => { From 344d927ff8bbd090b02967ba0e1217cbdb1776f2 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 20:38:27 +0800 Subject: [PATCH 093/117] opt: optimize icon --- flutter/assets/record_screen.svg | 24 +++++ flutter/assets/voice_call.svg | 2 +- .../lib/desktop/pages/desktop_home_page.dart | 16 ++-- .../lib/desktop/widgets/remote_menubar.dart | 93 ++++++++++++------- flutter/lib/models/chat_model.dart | 2 +- 5 files changed, 95 insertions(+), 42 deletions(-) create mode 100644 flutter/assets/record_screen.svg diff --git a/flutter/assets/record_screen.svg b/flutter/assets/record_screen.svg new file mode 100644 index 000000000..e1b962124 --- /dev/null +++ b/flutter/assets/record_screen.svg @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/flutter/assets/voice_call.svg b/flutter/assets/voice_call.svg index 0637b58d9..5654befc7 100644 --- a/flutter/assets/voice_call.svg +++ b/flutter/assets/voice_call.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 71dd2c96e..2986adc7a 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -358,14 +358,16 @@ class _DesktopHomePageState extends State return buildInstallCard("", "install_daemon_tip", "Install", () async { bind.mainIsInstalledDaemon(prompt: true); }); - } else if ((await osxCanRecordAudio() != - PermissionAuthorizeType.authorized)) { - return buildInstallCard("Permissions", "config_microphone", "Configure", - () async { - osxRequestAudio(); - watchIsCanRecordAudio = true; - }); } + //// Disable microphone configuration for macOS. We will request the permission when needed. + // else if ((await osxCanRecordAudio() != + // PermissionAuthorizeType.authorized)) { + // return buildInstallCard("Permissions", "config_microphone", "Configure", + // () async { + // osxRequestAudio(); + // watchIsCanRecordAudio = true; + // }); + // } } else if (Platform.isLinux) { if (bind.mainCurrentIsWayland()) { return buildInstallCard( diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 653ff37b1..dcc531408 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -657,12 +657,17 @@ class _RemoteMenubarState extends State { ? translate('Stop session recording') : translate('Start session recording'), onPressed: () => value.toggle(), - icon: Icon( - value.start - ? Icons.pause_circle_filled - : Icons.videocam_outlined, - color: _MenubarTheme.commonColor, - ), + icon: value.start + ? Icon( + Icons.pause_circle_filled, + color: _MenubarTheme.commonColor, + ) + : SvgPicture.asset( + "assets/record_screen.svg", + color: _MenubarTheme.commonColor, + width: Theme.of(context).iconTheme.size ?? 22.0, + height: Theme.of(context).iconTheme.size ?? 22.0, + ), )); } else { return Offstage(); @@ -708,36 +713,58 @@ class _RemoteMenubarState extends State { ); } + Widget _getVoiceCallIcon() { + switch (widget.ffi.chatModel.voiceCallStatus.value) { + case VoiceCallStatus.waitingForResponse: + return IconButton( + onPressed: () { + widget.ffi.chatModel.closeVoiceCall(widget.id); + }, + icon: SvgPicture.asset( + "assets/voice_call_waiting.svg", + color: Colors.red, + width: Theme.of(context).iconTheme.size ?? 20.0, + height: Theme.of(context).iconTheme.size ?? 20.0, + )); + case VoiceCallStatus.connected: + return IconButton( + onPressed: () { + widget.ffi.chatModel.closeVoiceCall(widget.id); + }, + icon: Icon( + Icons.phone_disabled_rounded, + color: Colors.red, + size: Theme.of(context).iconTheme.size ?? 22.0, + ), + ); + default: + return const Offstage(); + } + } + + String? _getVoiceCallTooltip() { + switch (widget.ffi.chatModel.voiceCallStatus.value) { + case VoiceCallStatus.waitingForResponse: + return "Waiting"; + case VoiceCallStatus.connected: + return "Disconnect"; + default: + return null; + } + } + Widget _buildVoiceCall(BuildContext context) { return Obx( () { - switch (widget.ffi.chatModel.voiceCallStatus.value) { - case VoiceCallStatus.waitingForResponse: - return IconButton( - onPressed: () { - widget.ffi.chatModel.closeVoiceCall(widget.id); - }, - icon: SvgPicture.asset( - "assets/voice_call_waiting.svg", - color: Colors.red, - width: Theme.of(context).iconTheme.size ?? 24.0, - height: Theme.of(context).iconTheme.size ?? 24.0, - )); - case VoiceCallStatus.connected: - return IconButton( - onPressed: () { - widget.ffi.chatModel.closeVoiceCall(widget.id); - }, - icon: SvgPicture.asset( - "assets/voice_call.svg", - color: Colors.red, - width: Theme.of(context).iconTheme.size ?? 24.0, - height: Theme.of(context).iconTheme.size ?? 24.0, - ), - ); - default: - return const Offstage(); - } + final tooltipText = _getVoiceCallTooltip(); + return tooltipText == null + ? const Offstage() + : IconButton( + padding: EdgeInsets.zero, + icon: _getVoiceCallIcon(), + tooltip: translate(tooltipText), + onPressed: () => bind.sessionRequestVoiceCall(id: widget.id), + ); }, ); } diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index 14af96570..bf7f8773d 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -328,4 +328,4 @@ enum VoiceCallStatus { connected, // Connection manager only. incoming -} \ No newline at end of file +} From c3b273a5add1f208a50c062f69906f45fc680156 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 20:48:09 +0800 Subject: [PATCH 094/117] fix: android compile --- src/flutter_ffi.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 84407cd96..2e6c450c1 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1285,6 +1285,7 @@ pub fn main_hide_docker() -> SyncReturn { } pub fn cm_start_listen_ipc_thread() { + #[cfg(not(any(target_os = "android", target_os = "ios")))] crate::flutter::connection_manager::start_listen_ipc_thread(); } From e944b776bc6ce9afe31ac3ce1b7e6f4520cd8f18 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 7 Feb 2023 20:59:13 +0800 Subject: [PATCH 095/117] opt: remove unnecessary config field --- libs/hbb_common/src/config.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 6032ae9c7..71dd9a5c6 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -212,11 +212,6 @@ pub struct PeerConfig { deserialize_with = "PeerConfig::deserialize_image_quality" )] pub image_quality: String, - #[serde( - default = "PeerConfig::default_audio_mode", - deserialize_with = "PeerConfig::deserialize_audio_mode" - )] - pub audio_mode: String, #[serde( default = "PeerConfig::default_custom_image_quality", deserialize_with = "PeerConfig::deserialize_custom_image_quality" @@ -1001,11 +996,6 @@ impl PeerConfig { deserialize_image_quality, UserDefaultConfig::load().get("image_quality") ); - serde_field_string!( - default_audio_mode, - deserialize_audio_mode, - "guest-to-host".to_owned() - ); fn default_custom_image_quality() -> Vec { let f: f64 = UserDefaultConfig::load() From 3ca72e82a0b26d8a1aa38bbb731f9c7119b66953 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 7 Feb 2023 21:04:50 +0800 Subject: [PATCH 096/117] new logo design --- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 1605 -> 3114 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1087 -> 1939 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 2097 -> 4087 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 3013 -> 6636 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 3799 -> 8908 bytes .../Icon-App-1024x1024@1x.png | Bin 10508 -> 49903 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 360 -> 669 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 564 -> 1344 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 779 -> 2049 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 455 -> 969 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 781 -> 1948 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 1072 -> 3139 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 564 -> 1344 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 978 -> 2846 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 1368 -> 4240 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 1368 -> 4240 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1962 -> 6893 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 926 -> 2594 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1691 -> 5794 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1839 -> 6468 bytes .../macos/Runner.xcodeproj/project.pbxproj | 9 +- .../AppIcon.appiconset/Contents.json | 130 +++++++++--------- .../AppIcon.appiconset/app_icon_1024.png | Bin 23562 -> 53345 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 2409 -> 5475 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 338 -> 978 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 4616 -> 10828 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 644 -> 1555 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 9733 -> 23370 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 1222 -> 2851 bytes flutter/pubspec.lock | 8 ++ flutter/pubspec.yaml | 29 ++-- flutter/web/icons/Icon-192.png | Bin 4103 -> 8908 bytes flutter/web/icons/Icon-512.png | Bin 12570 -> 25973 bytes flutter/web/icons/Icon-maskable-192.png | Bin 4106 -> 8908 bytes flutter/web/icons/Icon-maskable-512.png | Bin 12626 -> 25973 bytes flutter/web/manifest.json | 2 +- flutter/windows/runner/resources/app_icon.ico | Bin 21592 -> 1961 bytes res/128x128.png | Bin 1575 -> 75 bytes res/128x128@2x.png | Bin 2760 -> 10623 bytes res/32x32.png | Bin 493 -> 74 bytes res/64x64.png | Bin 2264 -> 74 bytes res/icon-margin.png | Bin 12179 -> 0 bytes res/icon.ico | Bin 34072 -> 48 bytes res/icon.png | Bin 12963 -> 60426 bytes res/logo-header.svg | 2 +- res/mac-icon.png | Bin 90116 -> 51695 bytes res/mac-tray-dark-x2.png | Bin 809 -> 1585 bytes res/mac-tray-dark.png | Bin 275 -> 535 bytes res/mac-tray-light-x2.png | Bin 810 -> 1193 bytes res/mac-tray-light.png | Bin 270 -> 415 bytes res/tray-icon.ico | Bin 4286 -> 4286 bytes 51 files changed, 96 insertions(+), 84 deletions(-) mode change 100644 => 120000 res/128x128.png mode change 100644 => 120000 res/32x32.png mode change 100644 => 120000 res/64x64.png delete mode 100644 res/icon-margin.png mode change 100644 => 120000 res/icon.ico diff --git a/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index d5d2c49c89429f17812a634e49dc5407b4782bb1..eac2fe7241381b7d162fb15323837ea7101e4a84 100644 GIT binary patch delta 3110 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 A(EtDd delta 1589 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 diff --git a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 41ccba607c6c0a5c84d367341ca562d2e8b5e264..d32c8f8e80bec54065a4cfdbac465fb35af8124d 100644 GIT binary patch literal 4087 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 delta 2085 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 qM%IAf*eABYz8wNklBs*w#M07ZwsQ7(k@#I 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 N002ovPDHLkV1m#B3G)B| diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index c35862a8cad79e203ff3a6a98b58612aa478d041..16cef3177fb50c285857dd57f2fb57647a41df88 100644 GIT binary patch literal 49903 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 literal 10508 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 delta 344 zcmV-e0jK_*1?U2h8Gi!+006pI?LPnj0De$RR7C)B|NsC05PJXm{QsuS|8%ndEsFnl zv;Q)U|L*qx%i{l@$^U(~|8TJXV5k2+l>ZWY{|9yd1atrO`Tz0v|J>~V$Ke0L-T#KW z|0jq4A%p)LfdA?9|LF4n;_v^p)&H>5|E|*imc;*$!2f2d|9@Mg|4^R)N16XQk^dQf z|4o3B(f|MerAb6VR2Ufrz=e^+FaQL=J;@Zd9fW6s{u|7weE|SUTlD|{v%wqOP5@1^1=A_{1*UzLfL-zd49NvB*PMY} z(gPf_1iEAe7*Uf~U|wWVfK9Rnd?xP`><+0n!1$6J`?+gsvIfvSCmEU~D*)V^nr@Zj q7Epll=f49KEdT%;wJiWZ+0+N!F$M@7z2^)70000iq_u zlkrGE_C^p1QjQLmh`dGM{x7hMao6oghdbCk5&mmV!Vm)v0|=DA99U`Mx8&+yEXb~Q`Z9ZRYQiA9XA%BNA>>#y;6n1-Mc1s0C4I!+D zBv!23S+IogtDT`xB5r z)J8JGAAj~r07E8(HWPR81!F}Hd(b4-KIl6<=)0SkAG>_@HWRNoGzIQ-A*UzmSWWr} z0_b=yJ?iPPz~LJ{0M$qh`=w4#ur4nvG5Ci8q#ifU-uswPQ<{C?iI{g)HW9vZYucU1F7oTG z`rfIq@=u(7Yfkn=P_B++!6VZ^sa@q6)z!5RrRU_kR&5n}Jz-T_ln4S>K+4tWS+F;V z6@8!;s@S}w9=FhJ2A&Nwgx;_^W@%!qd@0DNBg7ekl}Qe(TXe;r>@L;Se^@qJXBZc8^vQjTx3bl z;WuaHE0dBUAQ~aE8@YVFS|C&%KT`&WCVv&H;+#Rd zI{mT~?W!7fv6*|`Mkyc#7C3ltE7?-h9N!jJ<4s zt=|B?y$Ub7aQhCfc?3-yz#(SRNinCeg4|~zktKnnUBXTT8v>1@i)>tVruYa&$ za_Fu`5SD>~o)O!+<<<)_`TEV>Qnejc+n~*&)XUf`cHEJFpOE8YAv?G595}JjB=V_7 zAE3w;OKt5!ZQb)jF3EGpD0a9}e7MK+-F}gY^V9X>=0e>(X_f0TyF3RS4@PFAR!+|x1#db~sHt9i43A{YQ-WBE-7$m&ZROWEU zw~L$Y$z&%5n2gRnuj4QK;3qx!=F|c0@?k*3bX`}8Gi!+006rnNM8T|0F6*gR7C)B|NsC02X+7a{r^0Y|4N$wAcOy9 zs{dc6|N8v@M3?{Y_y6nj|J&>Tz1sh!&Hq1?|2B{RE{p#ffBz17|MU3&jgS!8IxBqak|9?=P{|a{h#NPk2)c=jY z|BS!?RG|Mlk^k4||DekMqd?oG0004ENkl7qjx+eexTz(c?%J*R;cr; zZScx;n5!UVZg<98%aMB5qa!{U#7+O6`m)?)0LAE3cTq7wbkyz< o1Dh_O-XEAC?es_Z$YwS250E_%Xl{s52><{907*qoM6N<$f=It2u>b%7 diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index ab315a4c6b0e580ee9e2cc9567ac156af384c918..18ebaab699c080d107c6faffc1c9ddf0819dec5e 100644 GIT binary patch delta 2046 zcmVU2gpf8K~#7F?V4+B6jc<*@15EAwq4q8X-k(r zSJC*wR3HLsHxWpve$o#{e7`lm1&tDouS89#e9**z8iTJd`oUNPiqdL8u|<@qhyvS| zwm{pZ-M;oQJCEa?-EDVg_D*+anPQBdY^K|py?cLi=bUrT|9{>!0RZ~9ndzSf$jAPN zK3DdihbA!{h)F$RaX2Q0L=xcOV6of6)Ywf`Rt95iR7VCtADa|fPjl^OVqHN#z)J`N zB6x`-lX#C@W_*Rq+*D$1F0(BzFrVcOA_3|9|+R0@FQJ&U-2g=9!su2OX2(oukp${s<3oQj#?Zil!+k+%qTtni4h~ z9e<@~;_>Rj^$QCNO}f!_L+>5p@B1;bCYJU%H@8@N&#n}*1@{kKRi*yHN zPS9fE5K;kW5H}%iZ*ujcPmz7rQALH!M?fpW_&wMKEX60VyHr?*EuB|fHI9b*A3`ND=!?8~X zqHV(*ncKe0Wpk?~8l0vE4JYmi3%;KMC!%7!7Qp_nu(^M7W35XEbclyH zx_AT$m!NaoSoGN{$8|OOCNrw_hJ~iCGksAhnV{l+xT$aQVNaopHp$bWKRhf3LMZJk z)PLNpOl&uV_+&)9wsWK}BBs?aa;IZFynib6BrS{6q2E0u#Gik92r9T=U1~Lim=j^u zkE8x6ak`9Abh`EA)DsH}GV53h^q(WrFN2_*s}9H9h-413V(N1 zA>%2`Q`l))sfDSvvpo?Z{vU;#T*?A1r|0x!r{wm!3?@z(@oX}jst}yv9wtguX=>=H z*lcBfsckNpku1w4L`~J?WJpvmk!C~{AdS=^pTzp5Q7#5@>8f3StC=&Pon}>g1$Qtq z7tAOagOpEZa62+eLN#=m6KAwfaesFQGw#|5^G}M3=vQ*PE$Tp0L)Xkx3TY)b$t{5} zlsSxalG71sAOwnPNy(ilBGvy_7NTMs4n{R(m4tWurFEAWnW}f3jLA{Rr2Ap2Ukig9 zGNh0inv_&aJ-F={(vnr5{BAJxHa`~+eLDAL#U=}rLzUN5kK2w?WL1K@?SHuB4?;t+ zF&?siSlBx_Q{_>(eQruQNrArpLiFqog{(^FmL%L`5Hu<7o;na#hM=1Jx(aJf&=-~C zODo{lK|1&OWoTu!F~j`kZ`}SNF(U+1+#UzB+)a@h^pfq~W%!XV)H0S>Lka%+Hr8e` z0D4zH|LlRN{KXVkPM*BGj(=Xb)1mLU0=>8!kAxN6^(DCDE_R{I0OFl}{0(1+1raHi zffU?!Gk(x(r#7cU7g+JrP4LWDDOV<}-`vTZJ+TPTawzs(Cr5y%YXUkqAuhM+T=#f% zC3>$D9XlhpuXf>%dvRR}g_sjyUq95iu$OjKW&2Kv>%Wh7Cj2<1lz%DQZX2`lV)`)8 z25mFrjtAkbeex50bOl;K8{>g-SpAW3WCSn+s~eb`FJ{)%Gvx*9PxehnJA1`#NBI4N ziEX6JxmL}+xzSl-)hvT&(@s?`{CczI)$UPPwN2~}%3}tSoWI-rGLy%JYv-YPc3F%B zIp`jj2E%DPUE1qaGk@H-G?>@b&!+bC6nw`=!1um5FcI%jS)Y*MkDrH`{Wz@^0xEZ7 z3A1InbM`&Ee4vk?0pEL4l31f2ls~6hyv%{Su83(}UEs);QguW3je&0q3{0kpUoCF! zGKk7uU&QWdc2+vFD>~iKE#IMmF{Ip#Gl09%&9<&}R629gpntldCABn4&6n}ZRBog~ zRB_*Ofq9G1menirhkoK}^yOjVB-*a5XXmBNjyGRpy?K#I*KWCP==xH8cmr(u7H!^( zC%F8%?Pm1QV)MpjW}-RtGUSsz$Rz*b8uZvz@NOsG+6Dd*ecTI+n7c1!A6RTEcjj3X zt<807*qoM6N<$f*ObL!2kdN delta 766 zcmVjjH|7olLTBH9vlK(M`{~m(>@%R70-2c7W z|Fzcte765?uK#AL|4*I&NSXiI>i^d0|Hjm&N~!zW+s+|KsoffVlr$rT?ta|EJFXIa2;}0006ZNklT6oujcIAvyTA2Z|rmo|!A`=v^pY5j>6ofxhOrP}MR;b^8dyif{B$s;G* zwru!(N`zipLVwAWD7{aiMJwJ;i!3Pkm^nhr|EB&6}9TXz0`ai zosjQcHI@1T&Xh_94US^&o3h@3nXK6ZN2p)O=>QcuFH`eCtA9g)ajWEmoPn%!0w&t4mn`im#qQlAA+Ih!=<@lRm(54ZP~@ZZ=kKLo&8_yj z6zFQNG9m9u*-aI$v|9P4zmoG9s)dt0aiD)Er%k|{yt^qLqZ@f+0{YMLs*6pZRCkbx z>Ybp;OpY7+y|U(puw7Ld1E8gh+XiJGv*|ZL8F%MB%I#+II)~v!Bl=lUU2%+cQt&cZ zb~#ReeeTbjOG#>WCa1gsyAQvYgFaAO%-^^DP_#z0Izyseb3wEP+7jtWsMRA%<5@`U wsC45}Lel3hqR-EgQ0j76GaSv-uNO-F1G>T-l-XG?PXGV_07*qoM6N<$f|yI5N&o-= diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 6d69c01e18397d3da443238836eb42b69cacc901..a8ee14a31e4b0fe52fed14f5e6cd05e0b625d7f8 100644 GIT binary patch delta 958 zcmV;v13~=91IY)F8Gix*007zX@K^u<1CU8XK~#7FwU*mY6G0Tl&+Kk%cgv+M2?bi@ zVnli8Z|*pL}R=>h(2k2@`4gIMj=5XRI~^v zrGVXDU}wf@yKQ&NN->6$KJ0d8e>>-#Z@yW9QVR1B!hAsI%6}b=k^ZnEV=4)#QskWr z`2XU5npCdzM>~e2U&0FT6u88XhYBm_Ew(hdtSj=&|Hci*$jNu%HeZyWBnxQ<>41z| z2jmO>(2+8!ZK(qxG?QS!?Fr(BC!w!l3><>LX3W}+ZVZLbEVdU2bKrgq;7yO@z!cH1 z(I6PDz1C46aDT?5+MBT`^*)LOv z!+_fxAlE-)(7e-IVYfcm$l%^kT+2TdZMQ2$LB}o5L)A9aKd#R24?w4%)|cntJ{eL5CNr*7 zYT?R7PRC{8UCNz(Ns`w*M|0T&L=@WcI;<^%w|~TvPBFu6^1%7$h=BzjZtmhe?%7($ zp!=VOyGM2Q)Ld;zUuU?rPQ=hx-@wgAZufF^cGn=8CQ$E2NaGKWzu}e_(VmHfk)~R` zr_x;KFd1-JT-=FHZwJw&BJOsR{r43n&Z;6<;{v+`V=>w}jK9kyaeQR#$tiN@R$H|w z9eFgEy1Z7X*2lN@l3CA$I}f+={usOBlgjh}?yc gcBQ$*e0@j#0g9ATCw(}=4FCWD07*qoM6N<$f@q4~ga7~l delta 440 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 delta 1062 zcmV+>1ljw;7_bPC8Gi!+002f7DP8~o0Hsh&R7C)B|NsC0{r>*~a{mo@|M2(!26X@X z{r~j&{~vi^H={|a~i>h%BN?*Cq;|52X*5PJXR@&Cr(|CPl5fw})0fB*RV z|LyhvrOp3|z5jHw|75BEOPl{UkN@55|JCUK%i{mM+W)Q5|9_y$|Cq)9dbR&mp#MFS z|1pgJoyq?ziT@>r|F_rwk-`6Num5VS|Bk@_S)%_%m;bNR|ESLYOLh_D0009`$|4TcIH9aK20HI}$e9v!qaSeFkP2MXKH>q^)ZC+fu z&6$NVG+ujpcYjw!S*-i8D`qHDdvxi}P_FiOcZaeaNU&K&*%~W?GnB7Qcoxbho0#A? z9Pp0c&_M;ip#q6Nj64rEwK=}cYMcJ4i`y*^V+LLjI5OaF=N4`u+jU;l5b$|nzYTG< z^O_+KffhrfX#-cO?gcdt0NB*D7P!Jkj}ct}@Te1$E`N$Tx=igVs|OU*XPl+>gT`K> z0J&$JD*>R#o5Fi0tP!a5mhgZ{M+BakG=7Acj$oWUkO(xUHWStjm>7;3CAKFur*nD| zjOoL~Hdfu;A?jfgD~mJo%^A`9K+>Jhh_!<}R+GA27eA3xLEQ{_j)r^gMNl)gkj-gQ zGm%I&6Mxja6xqAJV|=Nvk!?f8t{{uEre=vO4}!`ovMs2Y8ehR{i5xzaIhpm2??e z!NiIoA(6(SQiq8;6$@#qR5mc-Q>hwt>oBn~>VH~PS};+g(m)z17#c|!uo-fZu37N> z?Fxo^hKIL>SNq}x$>EBIoxH=M3k@bsOTJ^}8K=TT5OBVE@m^ma6*GHRG!Ki&}8 zsy3A^vb<9BRpg!i6%a*2FhtzllVHR%@>wfJjlMb#6lW2ya%I85oyR!) z{t@5q_G8AmOBiVjCU_qRELIiI^)Q2g#V}gx<&4h`BF=xU!Aptl{*h>^K z6^v>8)D;61G+7DfiI)DEWS7;3E`2IzYN^>r8o#C5Wzg6u0rgyY( zz3TDr(wF{-5SzJ!8!eC9G2N#M0$rc%eZD**Uj_N9mYTc@6BTsu8y2|7Z+M2?K>1_~ zd`0?GUHUIzs diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 5fc34ce9a95f150c792d5f88b422a1b32ba40456..fd3b01b6d288c9a0d2eae79824afb420cba33e5a 100644 GIT binary patch delta 1336 zcmV-81;_fd1i%WA8Gix*000A=FFF7K1qVq)K~#7F)tFyQQ&$|v&$;)uz4Q-sz+f#f zLBP$i87EPQy3FZxEbd{MW_$3B4JAu-2}`y~WCHGg+}`%EnCW8NGUGO07ZDK`C#;L^ z4;mRzD}xRlltL-}ckeknx24{D4yC0Hi}CkB4$Zyi)ARfD{eS&V1&lGc7be^j>iq_u zlkrGE_C^p1QjQLmh`dGM{x7hMao6oghdbCk5&mmV!Vm)v0|=DA99U`Mx8&+yEXb~Q`Z9ZRYQiA9XA%BNA>>#y;6n1-Mc1s0C4I!+D zBv!23S+IogtDT`xB5r z)J8JGAAj~r07E8(HWPR81!F}Hd(b4-KIl6<=)0SkAG>_@HWRNoGzIQ-A*UzmSWWr} z0_b=yJ?iPPz~LJ{0M$qh`=w4#ur4nvG5Ci8q#ifU-uswPQ<{C?iI{g)HW9vZYucU1F7oTG z`rfIq@=u(7Yfkn=P_B++!6VZ^sa@q6)z!5RrRU_kR&5n}Jz-T_ln4S>K+4tWS+F;V z6@8!;s@S}w9=FhJ2A&Nwgx;_^W@%!qd@0DNBg7ekl}Qe(TXe;r>@L;Se^@qJXBZc8^vQjTx3bl z;WuaHE0dBUAQ~aE8@YVFS|C&%KT`&WCVv&H;+#Rd zI{mT~?W!7fv6*|`Mkyc#7C3ltE7?-h9N!jJ<4s zt=|B?y$Ub7aQhCfc?3-yz#(SRNinCeg4|~zktKnnUBXTT8v>1@i)>tVruYa&$ za_Fu`5SD>~o)O!+<<<)_`TEV>Qnejc+n~*&)XUf`cHEJFpOE8YAv?G595}JjB=V_7 zAE3w;OKt5!ZQb)jF3EGpD0a9}e7MK+-F}gY^V9X>=0e>(X_f0TyF3RS4@PFAR!+|x1#db~sHt9i43A{YQ-WBE-7$m&ZROWEU zw~L$Y$z&%5n2gRnuj4QK;3qx!=F|c0@?k*3bX`}8Gi!+006rnNM8T|0F6*gR7C)B|NsC02X+7a{r^0Y|4N$wAcOy9 zs{dc6|N8v@M3?{Y_y6nj|J&>Tz1sh!&Hq1?|2B{RE{p#ffBz17|MU3&jgS!8IxBqak|9?=P{|a{h#NPk2)c=jY z|BS!?RG|Mlk^k4||DekMqd?oG0004ENkl7qjx+eexTz(c?%J*R;cr; zZScx;n5!UVZg<98%aMB5qa!{U#7+O6`m)?)0LAE3cTq7wbkyz< o1Dh_O-XEAC?es_Z$YwS250E_%Xl{s52><{907*qoM6N<$f=It2u>b%7 diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 6928a4e6d8c50182ef8a030468ff770cfed2c937..aee7e4321be4c7803a4d8c12683eb2e5f0062d0c 100644 GIT binary patch literal 2846 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 ws|rC?VLFh+W%U05c-&AcOx6 zc>nYG|L^wyoXG$6`v1}8|GL@#cC!Cfp#L_H|1XRG7k>Zi^#9@R|I6b49fAMV=>NXk z|CPl5j==wdx&LRX|5>B|PMrTkmj4lZ|J>~V#^3*ky#IZ-|9?A@|FqTru+#sm(Ep;# z|8TJXDv19jhX3X9|H9q>r_TS`>i>zo|6Zm4g>sOY0008?Nklm zhOojryaOvCEPwHCBhrE$X zsR95)W^`uwNYG_gUj_<82j;eI3gQb#%o~I-HBXth0)K!t3%aI>jfe$zS<6`i4<>>( zi|PQdC5!eB^t=iSIurb--e=JhdWFhNlP!ei^U54|4xKr(+@ysZuNE7ksrWJYI; zBJ`@v+MryG8Mg)gz!|%gq87@8ZAw+u?@X96MwydYyB$+%dnnPT5ci zXgI#?@Q%JthO>jaX%R+><7b;Y76p9T~tTt5ojLP3^u46U2N7seDq=sYM~ z44!F1!$~PDpfI5D0*pIz1eN52t~a4jfy(KFYJc8QI2w(wbChcNCPT3kQ`n=_hDQB< zPr+@X#3H8ffZCK&)pWfwhME^sszGT+$?u`WlagCET0q`WrWH~06^XN$y0Vtm%y4V? zowISN{Xi+~Fl*32!M4k+D1gS?WzK5Sf)Gvz%sWAA%mR1Uz<-*Dlm$EJIXg<(g>MkC z=zp<*fv``*ZN)W!kzR)d139>5w3{&0>ocz_7hF>33}fM*Ih8*Iu3Gp+&1dQM)*n0g zOtVt_B6CZOX%lv4&$?Vmf@6Ir|$?UQGUETTSlqKoAQL@0l#=hH6UJGah=4VBo z2dw~gST(~DRt=FZV3F4b*RbeBFtvH88$c6e*MlX_fCR~d2P>R}??B!HSkN2D%Y|qY p;!EBsrVJ$GWcJPV$86Z<+uw$~DCT@XL}vg1002ovPDHLkV1g^I^QZs- diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a13129e156dcc8e44937d4492ef206d7725d8548..2d0da17b1782ad67e7de6f7c5426199cbcfa8df7 100644 GIT binary patch literal 4240 zcmV;B5O42^P)@iGs=@AgHJ>`h*zY z)1c;gpqwixARq{c3hn}Xv9QZ3d(J&Q(_QK5nH`3nqpN#odU})j-@M(aIl8O9{rj)~ z|NF06B|-?KB=Mq5b_f1nBn4U`Nr9F~QlKT06ljSg1zI9WftE;8pe2$NXo(~RS|X7z zXoOI+o361_=bh9g7j>DWJH5zG(IlHRP!Uk4fMzAkQWM!4B3G*@(klye3MGWe?s#O8 z7if}3hgv;fwvdP0JjXi7HV>PJfe85N1%9Hyb6@kSx3mgyomn|5O>=XGdSa$FQ$(9Nd^alV;9rGC;F!g8 z%t9`&wG`?Uzsc6m%{B}&sFE;FR+0m{#!9WNa=cyd^676-)j|nIWPJrL7HhWYd#lb# zt*o=IthY|h(k{(6jnB|ZKV+#v|Ip!Gb=vvC1-BOkEkh719&5mFL&916pYLvU?QV08 zPE#+*H&4&fN@-7|1O1bWUiz)$ty&lDZ$l12pP#|L&c_GYaudTI311uJ7gRu`uynnMCXGl9j+mF^|S9qfz;LAX(t zfnGDsjTfzq$xc%y*6B_ZXbXwv?zeBfK>8Y?g9NhEYX^l+SjhlhJAV)c4azHvdWF^&+T zN!&nhJMXz`w}Yg++Hqr@K)qgEU|LmTmL@)o59p&mQNP;lBm;>zj%ARkUN0&%N=6yP z1$0$Ab^A7l!^<`zTj3^P?MMjA;)ZGG2A@u8A&T}1f6orb-I>fQV_4%UwRY3S3DXI-wImo=E*wmd- zi<54FUVEIZXrTCAC=ih~=czs>Ma=4}VIHpxMPIa#Z#OtUXmYvf0BfBRt{!aqYoT7~Ft;>2 zo~-Stbur^aIT}TIzIlE>qd43mL2o$WIo(3Xa-ld)jJa{_HOkdD>c;fx)@?l{0jKoQ zFpn49)aLWfGd8NksF;_l%hmB;794r0!MfmV2Y`XGG`XpV&$U}9`pNt>F&z`}or_j_ z^*0{hv)Jw`z-3)*R;(GJo1GWWd)0jy1sovW9IrobU3j)L&y_4aHsqzZ>+N~I34scuvowUvvXYu)k}># z5CNdy`O!;yLn9P1tY5uZJED)gD8F8BfB2N;N>T-2v362u(C}_?$6UbS!r`nNF3`^HgBVg>Zj$`AuP}bE+BX4EkS{-$M+*fX3J5X z8Ocx+_x0DlS?`SPm4>nQzvz2X(dPPHj)C_?SfKZv=NM=pi1h$o5^Y{6n(+0tV` zpZ14^qFDFU!~=9*8VpQ^Ab3_>y*}HDY$P(uJ59<+o13n-(h;)=IIMAsulYe2XGRQ% zu=JXu;eiJFwi3A~@b>)VK^&k$SQi=iG4g*RHys$%z)pr+1}weaR^0$2xU9$Z!n((g2fEO=>8gj0;mO6)>-T@8*G)#fWqGURWSh65$s0vm zu~<*;9lw$=qXv%3MF;9*cyh7y+T{V8PtuFW$Zkqxx?g_W8A&;CS@+WuL(Ev?!p&pO z7|fb!o(LtXURTL7a`~iz+;!0tL+aTN5S;~OD6#zXzq-6m0mwHMP8JdZvMO4^Uyx0 zFQ*Y>_6E4D%ggjaw?`2SdUOGt{R{NbsfeDOZ%ofkgHKM75oL!LJ-pwwsgey$qU^IE zi1h$7F|P>MeGZD43YQmVfn8^S!xKTTSKLZ0ogf!a4k7fP8t)Sa-RG@z$d%V!)*D9{ zRKl~qVn7#W!evv@qR+Tq^Xl8+;#&mwpwWUhpQLNs(SR)Y`|FA9IF;)iZKlc(xxZ*+ z>#Sn0MiIjLu3Yuh95Gw-imcgs{1#|Xqm_FYOukO!XToQvfw{v4GkX0PwQx6OXVdP? zFqZEJ_Y5ZH4OL9YhbmD!;95xZakY2TY0v&9wtN6`wV^w#jVicqxZoYI{2~%G)0sby zfq(iXsB1$C0_10iSw;BbVd~MH>_rU%qjGz}J3r9xoT7Uh!0bW9?IpzcJR%*VQnJxT z?`fd6)==B)DW?~O9y25d)LqsaZqyf=M1}?3szM|PP~1!8mHy%(`mtT%t8oKAFWbQz zCursYAu7&;Q3Pq(5*8wxBl;LdnLSc6!l6L;jQ z9w-$&2L>fM<(|v;(#yW+y0jy>P#`!+gkW_WVjfqNGzb*1&qI}0P-SBHCxfqsu`V(y zHjOoOXYp?mK>H}~^-~sKry7Z1JXlAvAqFMbI@y?oQ6&>Yqz1iw54~&;9c7NfV-2`j zw1l!|wD_On^g}Ybo3W7;^s>+C<$Iz;!1$~|82UuAe&HtVy@hf3R}0dCURL3!9PtVb z`6(yXORm>EdTo6EKZ``5pRPd9d>-Qp6K#&eI6go7q2^wchVf=ShD|2Y9zKU%wBsCbt-q9h#jca5Ocjz8li zS+5N$@YW>VJ;m|gU?kz7J^n?d;yk$sp9~%?oHd9-5SMjvI~D zq1$Uo;dMMU1-d-wr30)Pkriuz?k-iVyIE6UPWUY$DL|_fVC_seXE^%Hhv2Jvxv(CX z3169@ot!UGHH37L;m{noe?Hh&iB{|ZU!PAV>j4?Ce57)2nNrGi2$JSHnBmVTgEPv& zS9NIPLGbY@$avY3u_hpzTtLjfK{=ziTmMl-oFpe7J+=^zEd-Y==#3+A^EarWBeATr zbYSif#X~nJ2V^Gg_Ixt;dFX3~Pfr1Vn*{dNf=?^ajx*rQ<#@8rHG}CT%2@-5iFt%3 z+568Xe?txCGM-ohCYHbrGr{?GRB;aOt3!txP;HwS*8NOiR6k-|K4h%K;9gKZ8=@v3 zbg1NKzz0Wy2S@tobU0DfW#49S4IS{34YW8=rwiCU>{1%0-wL2tfix}5(u2Mxn41QQ zGvL5pu(w_=WH}-)(4ms9gQN1ms63g16Dslr{XZrt&=N@sv_z5uEs>-^OC%}K5=jcQ mM3Mq6k)%LNBq`7m$$tSHK`j=s#+7#f0000QC58VCcmMeN|L5}m-R%Fk*Z+UG z|0#(76MX;g_W$GX|JmyQs?h&apZ_t8|JLaLEsFoe-~XM+|9{fv|FP8nqRjtvvj0w; z|Cq-AjKBXvmj51t|KRQaz1shTyZ>se|6Qg3dbR&pqW}8*|B>(ussI24aY;l$RCwC$ z+l6+tIsgRV8Qj+0UF-J#k9MU_NcNDh%dg+D`p|d+fZyHilOZm`PHMduegl5Gt@*ep zyS|YGM-L7z?|(TEgL^(oVeitxRR?9MS>J+dDABX^8C*rF>L0;92jyD71xG$g)*6!_ zA5glG1aBRbZ$+?%A7Dn%#}D8}xWf-I#U4LI9SMGj28Q?%^2GkNd^(2TKdts>LtL8u zyc?6`&c(aFMbg^Dp^M8&rft5v$D0{HjN&FP!mWG!GJnRK9luz2GY4l^AM-oQ{TS?1 z76+VHxS_zus|f|G1m`oXFk^sM9g3`@jWaonnbp9n5wp72IEyM}61<8ibC2SNJ)R04 zQEEFxLCp!}8bTclCQTHw3@8v9*DV!-B?@SEsAA)lN0m+!#)h|4>EO+lO5Gt0*&dbF zc+;nv*MEYUDU~`mcw=vZht_^Vr5X$*7Ny#i&_PNg9#PJkLz}yxOwjj{ z&ZeN~R z*YGFI*lKRrMNfcPvNb?{cNE3QXE4(FRndT=m4AbrW)uYu@`@>lG>WT2(E&NRBb^Es z-Lxr~8aJ)33k~-aL@x3FNhc4e}Mjsfji_NcHK0A5E&jJ&pC70zFZCkX>&Ria#=Z0p( zeT@anx@NdXUQ*O3Skg7*GcLBPKhSgyvwtqM#dQ`Gbj>gM4*5M$bZ@>}piFxJU5z%SqRO+F?OY#6lz)4I zzCKmj(>cC0pGce4l_3$Dg}}bFL((H-qMtF z#YKb2dW5+~M1ioeaV)CTP{j4<^ljW0720(aGZ<5Delj=;QPj=q)E#q&Qi~;ubK(=h zXj7un!dV<{l|_vXGdj;W7dy68D}T6&&hsY*=361!YRPvE+a)v@MBmCzJFMvv7tria z_-UG5{7?EfxRge9!&kS<^nS@iGs=@AgHJ>`h*zY z)1c;gpqwixARq{c3hn}Xv9QZ3d(J&Q(_QK5nH`3nqpN#odU})j-@M(aIl8O9{rj)~ z|NF06B|-?KB=Mq5b_f1nBn4U`Nr9F~QlKT06ljSg1zI9WftE;8pe2$NXo(~RS|X7z zXoOI+o361_=bh9g7j>DWJH5zG(IlHRP!Uk4fMzAkQWM!4B3G*@(klye3MGWe?s#O8 z7if}3hgv;fwvdP0JjXi7HV>PJfe85N1%9Hyb6@kSx3mgyomn|5O>=XGdSa$FQ$(9Nd^alV;9rGC;F!g8 z%t9`&wG`?Uzsc6m%{B}&sFE;FR+0m{#!9WNa=cyd^676-)j|nIWPJrL7HhWYd#lb# zt*o=IthY|h(k{(6jnB|ZKV+#v|Ip!Gb=vvC1-BOkEkh719&5mFL&916pYLvU?QV08 zPE#+*H&4&fN@-7|1O1bWUiz)$ty&lDZ$l12pP#|L&c_GYaudTI311uJ7gRu`uynnMCXGl9j+mF^|S9qfz;LAX(t zfnGDsjTfzq$xc%y*6B_ZXbXwv?zeBfK>8Y?g9NhEYX^l+SjhlhJAV)c4azHvdWF^&+T zN!&nhJMXz`w}Yg++Hqr@K)qgEU|LmTmL@)o59p&mQNP;lBm;>zj%ARkUN0&%N=6yP z1$0$Ab^A7l!^<`zTj3^P?MMjA;)ZGG2A@u8A&T}1f6orb-I>fQV_4%UwRY3S3DXI-wImo=E*wmd- zi<54FUVEIZXrTCAC=ih~=czs>Ma=4}VIHpxMPIa#Z#OtUXmYvf0BfBRt{!aqYoT7~Ft;>2 zo~-Stbur^aIT}TIzIlE>qd43mL2o$WIo(3Xa-ld)jJa{_HOkdD>c;fx)@?l{0jKoQ zFpn49)aLWfGd8NksF;_l%hmB;794r0!MfmV2Y`XGG`XpV&$U}9`pNt>F&z`}or_j_ z^*0{hv)Jw`z-3)*R;(GJo1GWWd)0jy1sovW9IrobU3j)L&y_4aHsqzZ>+N~I34scuvowUvvXYu)k}># z5CNdy`O!;yLn9P1tY5uZJED)gD8F8BfB2N;N>T-2v362u(C}_?$6UbS!r`nNF3`^HgBVg>Zj$`AuP}bE+BX4EkS{-$M+*fX3J5X z8Ocx+_x0DlS?`SPm4>nQzvz2X(dPPHj)C_?SfKZv=NM=pi1h$o5^Y{6n(+0tV` zpZ14^qFDFU!~=9*8VpQ^Ab3_>y*}HDY$P(uJ59<+o13n-(h;)=IIMAsulYe2XGRQ% zu=JXu;eiJFwi3A~@b>)VK^&k$SQi=iG4g*RHys$%z)pr+1}weaR^0$2xU9$Z!n((g2fEO=>8gj0;mO6)>-T@8*G)#fWqGURWSh65$s0vm zu~<*;9lw$=qXv%3MF;9*cyh7y+T{V8PtuFW$Zkqxx?g_W8A&;CS@+WuL(Ev?!p&pO z7|fb!o(LtXURTL7a`~iz+;!0tL+aTN5S;~OD6#zXzq-6m0mwHMP8JdZvMO4^Uyx0 zFQ*Y>_6E4D%ggjaw?`2SdUOGt{R{NbsfeDOZ%ofkgHKM75oL!LJ-pwwsgey$qU^IE zi1h$7F|P>MeGZD43YQmVfn8^S!xKTTSKLZ0ogf!a4k7fP8t)Sa-RG@z$d%V!)*D9{ zRKl~qVn7#W!evv@qR+Tq^Xl8+;#&mwpwWUhpQLNs(SR)Y`|FA9IF;)iZKlc(xxZ*+ z>#Sn0MiIjLu3Yuh95Gw-imcgs{1#|Xqm_FYOukO!XToQvfw{v4GkX0PwQx6OXVdP? zFqZEJ_Y5ZH4OL9YhbmD!;95xZakY2TY0v&9wtN6`wV^w#jVicqxZoYI{2~%G)0sby zfq(iXsB1$C0_10iSw;BbVd~MH>_rU%qjGz}J3r9xoT7Uh!0bW9?IpzcJR%*VQnJxT z?`fd6)==B)DW?~O9y25d)LqsaZqyf=M1}?3szM|PP~1!8mHy%(`mtT%t8oKAFWbQz zCursYAu7&;Q3Pq(5*8wxBl;LdnLSc6!l6L;jQ z9w-$&2L>fM<(|v;(#yW+y0jy>P#`!+gkW_WVjfqNGzb*1&qI}0P-SBHCxfqsu`V(y zHjOoOXYp?mK>H}~^-~sKry7Z1JXlAvAqFMbI@y?oQ6&>Yqz1iw54~&;9c7NfV-2`j zw1l!|wD_On^g}Ybo3W7;^s>+C<$Iz;!1$~|82UuAe&HtVy@hf3R}0dCURL3!9PtVb z`6(yXORm>EdTo6EKZ``5pRPd9d>-Qp6K#&eI6go7q2^wchVf=ShD|2Y9zKU%wBsCbt-q9h#jca5Ocjz8li zS+5N$@YW>VJ;m|gU?kz7J^n?d;yk$sp9~%?oHd9-5SMjvI~D zq1$Uo;dMMU1-d-wr30)Pkriuz?k-iVyIE6UPWUY$DL|_fVC_seXE^%Hhv2Jvxv(CX z3169@ot!UGHH37L;m{noe?Hh&iB{|ZU!PAV>j4?Ce57)2nNrGi2$JSHnBmVTgEPv& zS9NIPLGbY@$avY3u_hpzTtLjfK{=ziTmMl-oFpe7J+=^zEd-Y==#3+A^EarWBeATr zbYSif#X~nJ2V^Gg_Ixt;dFX3~Pfr1Vn*{dNf=?^ajx*rQ<#@8rHG}CT%2@-5iFt%3 z+568Xe?txCGM-ohCYHbrGr{?GRB;aOt3!txP;HwS*8NOiR6k-|K4h%K;9gKZ8=@v3 zbg1NKzz0Wy2S@tobU0DfW#49S4IS{34YW8=rwiCU>{1%0-wL2tfix}5(u2Mxn41QQ zGvL5pu(w_=WH}-)(4ms9gQN1ms63g16Dslr{XZrt&=N@sv_z5uEs>-^OC%}K5=jcQ mM3Mq6k)%LNBq`7m$$tSHK`j=s#+7#f0000QC58VCcmMeN|L5}m-R%Fk*Z+UG z|0#(76MX;g_W$GX|JmyQs?h&apZ_t8|JLaLEsFoe-~XM+|9{fv|FP8nqRjtvvj0w; z|Cq-AjKBXvmj51t|KRQaz1shTyZ>se|6Qg3dbR&pqW}8*|B>(ussI24aY;l$RCwC$ z+l6+tIsgRV8Qj+0UF-J#k9MU_NcNDh%dg+D`p|d+fZyHilOZm`PHMduegl5Gt@*ep zyS|YGM-L7z?|(TEgL^(oVeitxRR?9MS>J+dDABX^8C*rF>L0;92jyD71xG$g)*6!_ zA5glG1aBRbZ$+?%A7Dn%#}D8}xWf-I#U4LI9SMGj28Q?%^2GkNd^(2TKdts>LtL8u zyc?6`&c(aFMbg^Dp^M8&rft5v$D0{HjN&FP!mWG!GJnRK9luz2GY4l^AM-oQ{TS?1 z76+VHxS_zus|f|G1m`oXFk^sM9g3`@jWaonnbp9n5wp72IEyM}61<8ibC2SNJ)R04 zQEEFxLCp!}8bTclCQTHw3@8v9*DV!-B?@SEsAA)lN0m+!#)h|4>EO+lO5Gt0*&dbF zc+;nv*MEYUDU~`mcw=vZht_^Vr5X$*7Ny#i&_PNg9#PJkLz}yxOwjj{ z&ZeN~R z*YGFI*lKRrMNfcPvNb?{cNE3QXE4(FRndT=m4AbrW)uYu@`@>lG>WT2(E&NRBb^Es z-Lxr~8aJ)33k~-aL@x3FNhc4e}Mjsfji_NcHK0A5E&jJ&pC70zFZCkX>&Ria#=Z0p( zeT@anx@NdXUQ*O3Skg7*GcLBPKhSgyvwtqM#dQ`Gbj>gM4*5M$bZ@>}piFxJU5z%SqRO+F?OY#6lz)4I zzCKmj(>cC0pGce4l_3$Dg}}bFL((H-qMtF z#YKb2dW5+~M1ioeaV)CTP{j4<^ljW0720(aGZ<5Delj=;QPj=q)E#q&Qi~;ubK(=h zXj7un!dV<{l|_vXGdj;W7dy68D}T6&&hsY*=361!YRPvE+a)v@MBmCzJFMvv7tria z_-UG5{7?EfxRge9!&kS<^nS%mdc1qghfsMDcs5CmEY2*ef3~KK5>EIMkAllPcLCJe-8J=G; zQgLwx=juwV&i95l4T&}-=2MY*y$1>puo!Ujgwn|I+{sWwBOwP46M|Qp#K0B`P}CK2@&1Wj(W^m3Q)utlw%UE zCi{HTS7gCGzj-fyyyF-SW{MP*`5{SzV)X1w0k+!f&NDPSxmZ{33 z2OUYa?97H#*!8ui|MAnek;IbBIOJuPO+8;Q{YS}0ie>iSW(pR!$TDE#&r7|ux(&^+8>XE3Kj3rxqhtIH9o?t?ZL-&eOsFu$supnGs~ z7}g%Na{Ox;(##;S2LF zHGLitX4Ns)K{lJaBuqNPg$9=Z{pyq+FX(~rnFn(5+3QQ2?{gc?4VQ0b)o>7!FYO-P z5N$2~8m%b%z~t5DFP6L4qXxAruzG-hiZQuwP2);4u6ydvRrie*13?+d#0&~DjxgrWtur*oE{F7RDv>t zG9P6r*EIE!aDxd@n17uyer$QSDXk zUN*WNd|LcMN>EUu26_e|w*w`MB;XC4h=b;0?}(YILiJ!SC{<$G2s}q|>;1%GIP-DZ zCL6_tPO+tDE^5yabXl$@NCFI*4fri8|v{FxE(^SzfpmCLT`#_ogFR>;)E`G44If*>gm zd29PfJR`$f?M_i{ucY7o4u?-!O{}2P&umuXuciywz}yucEtdPlcJII93B-ljuobjn zx!s1Ko!5QI5f$T2<|phpL6#xjyjTwwAE^qhQ!-tLn*9GRpi|x`-FOi&o0dP5v0DS0 zRXZ|RIU#TZ@Gpy_8znn|RDxUuy>!Qld;&ZsO9S(jiC( z$fA~m6aFf-^yt0w#-_V?LLh=Puzh66;5s1cFs46hN%nbN{d11!mx_Qte~P(sz9UQ{ zyz4?UoJ@0MxH#TM)ERy`o~tb1D=8NOhXg|CJDZeBc(b@ zx#A6lxV|{ppx7c-B*XW9gj;)0_|HF9hu_)pm7x2)Gbh(dzkg~MwW=x5T0WAMM>QlG zLDUIlS9({w5%|;9$Ixsi76@MOJ^%Jw#K%e4>qqP0%Y{k%7ujfUHLw#r2fk|o=E+ku zJEDGnSBEy==VKN#pEZB1MbL!#gS8z7LJkV9#MM03E==d_5zCa=Iy}{Jf7(*`tF&Hw z!h4)CH8IX&Tf^D)EqUY+OJv6+U8vyY;CaZBYt2|lx^)bG=pu@A|CYjz441cf4P5B-Tyk2Q7<-PZ{Yfk z;=;qtdUPnds$I#-O*L^P+$0KP;qnq%Mk(a7N;HK=l*NmWg{Xo+2M5a@9P=su6L1o)PXnH0FVPq^g2xor6p z7U`Q^LO2O0jlFxMY}nH+n4~@ZIL7c{~Gi`T*Zh%Z#?DmGLRjLp5a< z0i|n~!ZsPX4TszDGZ9^wqL@7OL+0zAj%h>%$Fs7Er=tQD*XO#8|J2(1Ya>I^V=o$3 zf5^&tsO>c}uEIt|^>w^r6o=0LnlDTyY>0r+kK)$uJ;f}j&rLE>C>5|!MJKB-hJ=UH zaStHeOSs+HlI!krTu&quE>bL0(VCQih@*qr{#o1nLGov4daB+Gk(XmVv(h|^h`-sX z*HJ5z*(~mGULd^+9HK$FyEH}hZiKfAl!!RIOaI_MkpR`8_?0lq4d-X?Ys~-Dd-2(G zWtA9ml!a3TJ(b{WQ=m|W)SD(X!H3j7{`f06*|bbD&PdN4fzBuM<<=y{f8V2tW#c^r zTq>ivQ1kbN!ZT^n_ftzyUIRfa@FrwSb56nx5$@teouz-TfPLnTs{~~*a|SxZj@#?9 zbJVk~)64;)KFwcy+m0k z=4h@82r<;S*Zigcj)KI~E-UUYZ*-OS@CZNhkoo7Yh&AiL0?ARRTVE*Oq<`?tSsBk{ zJV`!rN~GlGgjDb<{HF86P)hrFKN^bWE8w?0*Y_Y>Hln#+xsJ~LM~20*fX7=IN-P>3 zSZ_UJjt!+t^RI=7g)RQ!B~SeK=myp!cVwdq5*>5;T10ZHFb9@f!*dCkQFzxuBpyOu zn`&cuZiU%%_c3%6byP4Jk>ESA=dYT8AbK7wbDu`Dn1Pr^{p=fol6G?D)x$Kr9{;-I z%WAwqZO&R!2o*kmt|Q{C?Ik0r1{!y9!tDi&I+Y>OxL`1Jv+`B1=VZyTedf8`^-_MOwby?#h!{5?ukadZ4<8SRUV6IFD+u7yn~MOY@u<56-4t&fp7o{m&o?qK;$da=J17EL`nu@bA%3=iyV%!z1_R zr2(4?9VpT%=p{yrl7~fX&97%200n%0Th`N2I#c02B&|cvY3v%O{?a+xkFWc^C*k-` zdvj90c`c8D7_d0RZPoLee2FL3L0Yx?)Knb5(~QkTzWR3TaL|jeN)Bbz!OipsJ3ewI ziq@Z|%QRnS3kgrnj8_Tr81u{ZJ<4!Ja$j!0AsN z?ZN#;E0NuhIX!9g5Kwi5U)Hf`d}p{VL9H#pRsw@=oy&GqdT2!R1>F* zQ1>xQFO_lv4Yuj%<`1{4zZ-Qsf{HO5&1G7J)`}9Qol=@|hAp!%&z9dShbcppFs~8P zqhiC%aBDe8O&CTKb*pW6dgGQ=g%LL=JEM5kd*5G2bfR`gX=b_;Xc(WD%+|h`K>SpW z@4ik3?-78=UvYhHSYY~eRLgC!!}Wx@p_=OnokHl6iy9#fYj?L-!8FoykgSsw+ za_htlXqcn$f0bWYmoc_nHWEtPeW~?Tmf0sbGwAZp_7J*~XmNVv+u}UBhuJ{bS!+Hx z!deh>{iDs8_?8au@yVfMM|ccx+CGWhhS93ud51y2)&Y;xoNr0)Z}N|%ntW<^NRBmu z_J@T3Ea@kB=-8wuj3Q(lGM%d{5gy2&JAoR~ex$seh*aj{h!YQgKI+6;qYnEERo#1G z?c}SF;+a}UHbje<+{tVPV#-o3o@{PF>6J!YfL?+i{978fR6C@!)R+n~f4#C8wE4Rx z8?Pq0S%)6F>jVMdi6nMcPs&L+diYMBJzL5IjJ%DyolmTT;IACDpjJlNbyuA2<@N0- z$;}rie<$$-Wa9FK-MZi-GAxqucJH2ahtUO3XZ;~lsiXyAw7%dqV_->I@MXkne}P!f z_nC4*fsHdlDt0@$P4pGnvA#d!EWo0S-S^dLJ4`yQLM?ACubpU`kc%$gki*6a{nrAk?V9xYzNoq z<2dZyxkl2b)=1+&;X!_ktLlZ);J~@>1Qv)Jr=4@pqO7h_#Olul|CP4q&Yc4PzTR#n zJ!_~S-JN|JuRY143B0@c>s0JQetzrL(x-&~{5rG!@EX3KEt59nHpmbV{8o`TKJv1N zAramGWVv-Xc3uX$`Z2?zZq91@&l1x>2XKi<7Qx34!2L=q?YYe^$u8^$UR{_DaaJPF z{x(Bo8{OddD-;7Gz*{F5qUGPPa54`9!($r&FY}xfg%TiD5%$4_Om~?93h|c(8;e{PTbXjQX_cKV0Yx!_B)E1#qPN3%B?KvtsX{=rAIHf&1#tk|Ga z?D4(u*AjKvK1!ZbV$fSqn|(!(r=Vb| zkMQeo#r9LnVhzyTPIRD@LY(>`+iYYEnmT+X$z|p%Et;U*NgNI!T1Yp*Lpl#7^U0B3 z%H8tt<9w%$GuY(nYF9F!QKhssvst;rh3t>h5D@DJ|Not#~u|xg%ioE9oS~G6NBG~ z@sKzxVNt$KN`AcJ_SBaVeTYwkb8mDdag3Qv8S)^3f1eHJx~XoCTgN-U6NmdlE|(Po zPH7s(Zc|aDThh2^P*!lnd0*m-EDjc%ZrhSmmA6rZ@ zBZ!&Y)CXQU-Mc#FcO7Fm-m?v?SlNN)`w&hzXu>oYH1u0?;;W;ZYS_ig6DPj_)o)u~ zo0T+9pO`S5b8;kX_CJ=nFv__7%Y^Aw3G?m2T+=a~SMt$g(R;_u&!!exfA1a#zvvc^ z?j{Z%$vza#UeD9+w`Fq6!d$|P^CeegY2(A5pNE!;$>}mt%g8}S1j9_;Rq+B-ox4=U zILI0gRVIQW_hi_-a)aeJ@8|n6O)7-2UyPZhW&GnISJ4z<>H1LKKpZTQTL0!rq{Zb= zg?FvSLy#?aBA_z&#iHLc(eXfLpnCh&Zz9JTUlb)KaGpY0s+@ zqSSZ=X*w{;d`;Q;;+3SVK*c&bl+Nt{n3dUr(L@v^tlA71hjqPFAFLm*fa(LxQutEn zu=lFz&%}a}Zmp()t1sOY*KJdzJZg^fSZ=zAIIxvYR;v!;B24NpUD4E3n7o{>JuW*N2*VjP1XTOLaO zB3ZKhY9`VHZRW%} zrJ7^CO(Z%{;G6Kl>jDM{)UqM4Fj&|2s{ox211J{2aYH%+>;^B!QAmv-dYpt75&5M9 z&pK;x6etR<<<=&?0dnZH#o(3!s`N2$bU|@KJ^t- zYF31HtjFiS-NxSBvRAl;hl}!0&YC%7N($OusKt8d=hP?Ke3xriX8(`lq7BvO_b!ow zJo5pq=b>BuD4%9#{P!U;pM}3-E{n4bUs^71&R^a|+BZ6lvuRDm@qu_XcfN%wo=mu( zt#);kQc%F! zpUlh24xI@Gwj4km4%%|~wNv~%Dd-dEtZ^j!L^2M@12ohcktg(FF~U};v%}S$0{+k5 ze`#sld3fUAoidEzXfJxgi zl27wvRM$NypiVR@%PD^eNo_4hd0swobSD_6Nik#o#63gj*QbJ>^JXEEXbs0kLSyan zLE+{pBIFKTxK22C?&aya`JLtZcK9dx}MO((-Y-?rc*~b zZ>3dMZa8oFWbNCve4im3=!-GAl{`yXHZ_{1uajSUl5Qs+BV6hcc|plU7%0f-=!BB!6#tN7unO>_e(bL;H6siE{c8 zu}Im%*jL*4V_v~wYtbZa5%{I7I9=i(S(a1dcD$3-k*bW1*^Kf`gRnvmS2ZpFh!+9^tzcKIn>(r%kYePz=;?rP11rtpTkLSy!L0 z01h@U5|MQ-jTLKv`D4=X0TvaukILV8GX|*OyqcvEzlkI2y0(rFIe+kK{%=vw_6@*p YfLlQ8cmVrfj76gLL|3gs)h6u!0N77q;Q#;t delta 1959 zcmV;Y2Uz&+HL4Gg8Gi!+000UT_5c6?0Hsh&R7C)B|NsC026X@F^ZyWf|NH&_A%p)a ziT@RR|M&X;3U~kD?*G!}|Ha?`s?YyqssB)(|M2(!kHP<4rT;Jgd|FhNqX{-N6nE&|v|MU6(q|E=H%Kvk*|9>2S|G?b;o5%l!yZ_|y z|JLaL$l?E%#s5H+|IFk6uG0TAjsNZS|Ge4%Zm$0(hW{CV|Ig(AT?7ty000KSNklAkY0rSIPwrVdOf{DCKufRs~KW$fF!+QFcuE9w8H|;9mSJ`uoEBwI}I|hbAeB=V`9e9(SMOszWV;7h7luKoh7LW`6zkq z>-5{x8wPGK>5s3E(2#0eKB&~vBlJ8{&vRWt4qC0!z>|WOrPI(ZRj|0vYn#SOv{IT- zi#seT@=4$pt%3yfAje`hJ_#jg1tb>SVG(x^mn3$B);-O*Of(#va(hK{KG5=LmKx%~ zOpoTR(0_7ikDt}u<#y{l69wSCWePJhbIR@g-`NK*6jsmJN-}bi=l6kG7R?A&iXqZ8 zn#Kl$Pafk`>qt$wVywMw(Bxo=bTpqC@4-H3dL5*pJf%X=sdf>wCQk@@%VY-ww{im} zE8n5lxbaw zI8b)hy*M*toCPiy1^R{3hB2x>E;gs^!Qea_x1TzbrD(f!GV||TP94>be&wb!S8pi^ z+O9H#IW9A%sOD^$t>$o%5+ysMpY7@_Fn_r$C1YIZJxfUuml#np!^K*Z1WR0?K!NAu zV!lVo2DT}fpizUroV?Np|`E(M|Pl($G_p3dkl zF0C|HL>k?lni3Sf@Xw|=ONQ=KQ-ApmTps5MQYZqA_c}!l{Uuzf4E3{=cz+p(2ezKk zxrCs9l-P3hZ;vUOM7T(2LQ#Z^?38a^ee!n{t#Fy1tIMoIhnFDVCzOqxbZAz6jS^q~ z>eYfVT5c~uPLB@m5@wgU*pTW`ADq8;X);c>j#K64jN|z!ynbpKoTS-jtXF98pBd{R z#0g#l3RII`yfR*O1Y6A_6Mxj9XZ_KM#c>cMp(vG+I74~~=N;_T#s_L{=c)|~hDJ^B2P}ZZZ zpdzG33bUPV!XTxVndw#E*qS13HROdU?Kjx(eOl_tIihG_;6$dtlYd|6=db=kf4QZV zIX(p`J_!~?-ih5aM=OOT9D3{1!T|sBY2z^mr~C&dTb&kk39k7ACMurNJ%X-*bEZ3C zf<69an8Z6G72~OO(8yAyR#OlaZMz3Nt*)VQ0m}HOuZndrZHLg^bp~i+AIDR#h!muH$JYd)9U|z;wFy7 z$*^H?6P~)OfSHG;stkS4f?n@d9Z;y6A_%@&h!XYecYDBVq36kYI z`_P2K8G4#b|DT{)!rkcd-=I0hod~mEL3<(Yf+_GTXy)H>2h56pf#dT7C#O~5KL$Q- zoH_7&(09>8%!K6rJ%p_N+7*cap4 diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 229bdf563dba4a57d01b59bcc19522eb674596e3..76abd423b27687314d56951abb83d5f22006ffdf 100644 GIT binary patch literal 2594 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 diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index caffb26a36f60caa3ab4f40d826505730fca1ef8..e08138333486262d3a505ef532a2827709005d41 100644 GIT binary patch literal 5794 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 YEi`7===YERv1c?@MNI{SoK@KW0OTwX%K!iX delta 1686 zcmV;H25I@CEt?IH8Gi!+001u>&=UXv0HRP#R7C)B|NsC0`TYL{bN}`F|J&>T4tf9l z{r}A4{|9ydu+#q|h5r?O|4*I&E{p#ui2v{Q|Hj|{I+6byfdAs}|I+3Em&N~IrvF=` z|38%f=kot`vj1+Fynp4#$YG^idA68A_8^6P)48{3|ko(P#DY&NR^s5T_Wl{V2|$7hi*IdA;+-N6k5C8dw;#sT7UJ}gK+lEEA8|>Y-F^j zQ-lyn4O}{OgSDYThq`f3SZms)V;R#Aw5MYy zQw@PGX7Yav$Z3}pZacJb6Q!qvy=(|do!V|huFm6LzwZ$}ByjwGsH-xT^ z&s1Y!xsW|TQ|F%P?(uHK4B-HJsy$|y<6VPU!Y62PubE|!cL6gwBj|7!%+$t*duD3Z zp<=~MeSA82%+#$y!4tD=C-~;B#Z1pGtaX}9=YMa0Tt=2&_L#IbXPVbl9fDx+95PKf zf~8xg=nZ0+boNcA=mxNI7t+XoLaJc1rP&Rvv}h9UU1aGbGzuH#%*gHm=XmsJlAyS_ zOM`h8=V|n4@?w>ulWlOecu9k9jDq5^UAf*FOE%DMup6blHbODMD@AX}$xSF5p^&NC zY=64Cr)Y^ndX$W3dh4`zg#zvOwBO)04(rw0lau+Rx9ctlPK1vULcXCL{vTmH0- z^{)>tOn@>IrckAkMFy}a@XIo+uD<${u zg>@VX?va<Z>tRO<<|+FrAaU;6&%P#|$kOwx-O~y2CfKzJJ3k-45(6 znaO#;r|La3Ic+GoikNMbryd+Y#c|9`efdHv+dxVEm09L^H_A~7dv{G{h@`86YzIXn zhv|GFktIu@>SfDRUGwR(NRF$xC~ia&B)Pyv*^?yf1AN-n{3;j^xEK}Zd%>FRiz!!^{{zcZ zn6|aUlY(&<;Lo{x>5po=BmA*OM5$opqK-dwd~;|h7&#vX_!C#PrNi&Q6VCH(t>NM~ gRcg11#}oesfRbFCP7h8}!TXrT_o{ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 751104548a96b25993043b95b8ab079875a21077..46de51af67e1c4ab5b129af9ac5fb03cad3e972a 100644 GIT binary patch literal 6468 zcma)BS1=p^*HxnT5SU_wYU3dmql3`*82fnK^UfbhTAU2h+tM?+jJ1?u zN6{l^(&p6E35TSi$UhFEXX>S|@YD336VGhv%casWT9NJ_wBOa&_|-xJ10WqWkfXYq zlWLg|QUE*{UU$;E?%s8JCv|G1lE}ZCX)loMhx>^hZx8HP_6gRkCyrPGGhTi)Ob!s? zQk7=NDl1AF)tgfJPp~Oi(rFd$i09-RHROhS$M5j^`4jdi9^CH?dYJaFL_B3jdIlXJ z0;v*smy6{0hpCUd_+9}~ZoV^)YvZpcScurG<$k2amhN|R`$=O@L$xG1bR3I&1gdb-g=YhkF=HFbdlcwJ3D;t(VGTYDsv2?#E@@XugCm<(tMoyjgYDkmN zwsCA`kuGo-YS&`Ay=q=Fa)lRR*h1zWyh~3?&yL_-z=E?sUMK~<4^+g7=akX(@i5$= z&4!WwZCSsL_To5d#AMuO2KIfO<_azAN>!B=v+>ZNlaRB-;jMm_5cve+VLSkZA&~_w z@m@*)FuKhL#o_p6A=?B{r2oBUvGg(7@)UM`jS?;NhR&Uod3p;b>t!azu+&J>*fW|D zYr=-V@KI<{QqaXN&L1u9BgY#VM725F%0}Hp4nSQY)c{0Uu@qu;eT(c_%Ny6^F&zPt zc2ChE+5y%wlfoVCfc{_IRVH|k?Ue_U&K)uxLDKr}UX3YCz7G#XfaL0lPd$+ZUljTM ztE*Ukc8J5RMG6ort0J9Fw^0o0uADql?`LUz0Fk1h`=4(OKpEMHd`kJO8xOEA{AmPy zw!q_dA!xR_e*7xPTrK*&s-m<+>H6Xhv})#aP}mlB8ajgB4;i--N`YKG4L$S$`1u^? zTfVPmKtjGw+MvfdiU7<$&8!a1N`kcn{^SXoSSSaY`@*Ta2SdxE^li;=XI1NmHm9qi zO|_Bx*q&p2BKp$);%`}AVUaaDJ*@7y`QEv>RvKnHu_g&#njb%`T!{rreH&M&WZ;Y< zj3CPRck|3nkBu>Og9<37DVIBoq1P4l%_Xsq;4ffgHeC24U?c-M(MEUW-{% z536JxQw*vgo}rb9F1Np>Q)PbFtVj;9r3wB`YFz9GW5qVAt9asX?SZ~WISc*>l)s@m zLP`Fzu6V_L2b`|m@JU96199ty<+dxsXExrL;>f=k!IP!_Cc;2ZoxN~a=}N}+492j0 zcqth%!nXJ(i%Cf_I#wZbZ!#`iCs&fD(BW;j=L)*X>9a)5p8QK`MKfN@yZSLE$$Zwp z2<~~04BI(8v+9BS)>ry_;*9J>5n($x7b_vL-pI^0?IT&DA zI6U%dvN^vbk6aM^^oHxw8qj^iWZb}^`KsMuS-od0NDN<4cEt_$f1v8azVs4+Hq!8| zlN^OVxmk`0l9D&Zl}O_A`DW+pnA ze_;K-n*(`-;;~6n@81Hq(8W*0ND*pM+3-W*uCYUF7C)!R1Wa&dBnj<9(&xShsx1+B ztfwgofPeFJA4^Yt6(!d-aeBc_Du6U&GnEZDdFy6k=v-i+q%~EgKAs*(V@@n{WT#E~ z`wQN|kj!Ja%p-o}USzSIpb!sl*9)f9e~V;)ZY(U)iRa?7+T+dWvP2Y9`Nu{}E4OPB8jUYGvzymqMPQ5F z!gIIQ4ekaBE`cDB`yIdLuQKMkAEBo<^|{j+YON{l4<1u9)8q~ZxP`y;1K)lWkcRJK1@r1*%P+171cx~XvF~6?7hOrnf+V%JN zl!aS<4#;_`t;a6McJ^F@Nyfp*VCrm>86&RaLF;a0ZSG1LXZhRMbmA@9)ooBG2`Ij| zL7ib4vf3Tc)Xm>k;cU4f}cZfD^Fu? zb@}x+Cpny?UUC;z+t2NA9UB?CJ>CNm$Pd`j!$zDI+g|+`PRt0)Nzs<6AhA`p67BOE zJ9mQ)e3R)9&}{|14+S^yx8?CXoFnj&9EE>t*XkRLmyJR?aABgse?V)A+<$j8cw?^J zT7WzYV>0|%2;}Qp8HDs6rbCg@}{Q?>IZIUXxHISP5zA-r>il*wWwt7fdU53CLJ-*J5fq7 zIAVJoT>7w5)}ll$i~((lzrD$NE4=hO^y49+6QT4O3P=}S{q>3sCQ!gFbw);NJ z6GpR%j8;R@1e9qEaq$eA@+Xt`*FS!$(ed9>!z!tbv09@+AAY@ocmaLH473WZ&+@Bs zbf1&@E1#^IHz!)=r86h!?}Asxej-Ei#%GAygVB>RI?!U?w?bs)-#xBdclbD>mK~>` z2BqPI1XNh~qMb`A@?JW_?INz5{Y@M0lAT|&i4aypgY)8oPFO#cVFf)$F0yJUk82NBO33RKQwWqlR}{uv1D#-TXIaHFIQ>GNToaYU@HjT!ZV+8h=VJ zjCcvJD|NT26I@PG1bvg_41CI5=sv3PP?G~YN;A0wEK8NMyY>l1hO7$hnySlaS4G`^ zaa?MYk~c%X2z*MYg*0E4S8GtRq7Z}keGVbxea|W_Q4*3=EdAWtVW=@h$#|9chlzK0?z3`PayDETv zVGH=^rnp?onaJ%<-=csvyXP|^PK>c}lDsR3Qm4eY{pDX^uMHpH=ZyFv(yt_N`M4%Z zYd)69ghp`c?NKW!cmn{l@90CVOx#PedS0`Z$!zzZAR9f&GmQ5(9YGF6m+x^fMYwv2 zX8)qn)w-s5{ZCpxAgXjm(Av^quG(Jb&5b+SF*3vVm%&jSQyF@IzxdtEun1tIf=JFM zc~9K67Yxtd*`;}CQtn0q$}`l02LM+cLfDpBAl{KkF43}V3jQ54925op@Qq5z2sjWu z%O0bT9Gw4CYC&su@dA;>dAYNuDNy|dLZ;PQHI<+EBP}WZD4F|w=iY9bpt||nw3~6D zVL|1vOnEriigN&u%q~PKGUNS18l$_w0Igk-*dStWP?d8wr=1B;szlN;w#F__S*5T6 zl!o38r};^^y}33}Hgd>I1_hLv=1dP?8=ld$-{X*HM^i#fLghOoOroQ9fu6%F%DB+nEK zY!HHD9-Vkobt4`Kt>6}pwgIh|r~@INn*s7TAh;oZP9r~&tG;n%=Ovxfv zy2x;vO9+R4i}kOf<-fFH$g_wrVSJa~$cpSRecuXL5%AN~s!EWaX7jNF{pG=Zf#EL4 zc)7daqdiuskif>blWMAf5wb`WD%HC#479Di^C}Kh*bzMIRV)x<-Go!qCyc;rvUH&i z63YSj0|kUeC#%^hIg-G+-Dg~ojD(@b&s;XyoTUD-4vakdsZXFyX1_QS)xiw~?0bgb zCPa!^PvBJZ7;v=5h`K>2kcs)dBXOF^CHGk7$H%t{!Or}Ua^m8XEX<1#q$ibJ2 zRaObJ)ctT40YN4!_dUyY!c-hWSzovYI3SmJ*Y@Dm}Ia@+VL)tm(Mb%D* zjp6+;R(tW;vWIzPS-)BC`9IZ|0K$11eL*X;oiY)sUi@Q0mrV*X7! z>d?AgVTS?vQfh|2v?jFw`z(Ds;lZ7f3P0 z8M}_6D#|BOpobFTb;SOD)UY%D-JEZkBu!t&O0QOmh^cz74ri2WD}%;Ix(M_HFldVT za`ehLD-7xfscE$Xms&WhS{A2R-oE=pD^H3_i2u5k4^vkD$Mu)>x-JT)Gj*uretia(+mI>)5L+{^c9^qg{B{f1@Kw$nsv=7^7bnWkV zp|6wnhpH}CmltOwt^7!1ooGJ+oEsdPySeMtK#7X=9!|$~x`Bx8O+Za^wdZ2r?Ecr- zqoe6dAAflDz$UP`Kzu~@L)Gcxo|~6CkR4lIu)eR2yP5vM^C#{zrk2u4r{AC%v{sHo znLBVfrGZz`Js8^_SJqMod#e6~1}NHgG|IRb6}r01gp8A2dV2iR1PRL0y@oko^$GH( zUd(ETgfO6vR0TKar1V3un+w&d8fan|B zhlwxuW|os*28%vAg&I-#<15pXKQwUvp(izVQCoA4I==2P=$Ov#@*iM;NM}%Vx8vV? z^J-4p^!O|$K+ox$%W_GwG_2VEc2q6-$4j)& zs#kWTQR{vONs`v~Nn6VaGv^7!7hGxExBR>PcoIu77vsrq_ur5)&_{(mi6lR}H?r8> z1oD=IH0f^Ly{pK~>|Z}0_qY^F;$B_C44N4GOVdkPTOn-%0<;A@x(U*ZElzy5duk#< z#|su1=TR0`el^LLfJx>?*Tcd1(SW9-p-_CeUtnvB<`f#OtGq_7YrDEfx0S+ueq3Hf z@=`y5@obpI^5*GpdL=%myMgS&-*~63+t;S;Ct(|gBllecxO<<$BUmDo?&PTVAr%2o zJcyb9pYMiLH@xK&VmBgR+nhqW#oQgzt|S#_!z5DBgZR64k(Ra~k5JFHo-NW;qH`i+ z7K&MVH+~wW2p*(m*J99AVtFU{-6^cSJ7~`xdt@&_@>kw0J2r58GA+D;SE$n?OIq#d zm$z2%NTmA%)9+;+#PI<}=Rq{9yeCox`=hxV@XfM?&b>Z7bpuHl*v8iMYR)&KrYG{< z3p#)6+UG?#mOPE-?{k%eBr0S^SVD89J(z`@cz<9516V$gJ*2f~-#KzrNRnx!z*cb2 zCQO&u3d7~T{|WQRZz&LZ9!5y<@o9r@HVHF68ij|LknK=7E{%(tWx533B88`Rnj5J* zR`=#=DqC3cl46DmrDPwrG$!&vi~8SLhc+Fyh{1{9v%xO}_W@oOux09zVSa_P@);-o#N?9>F;^uX!1XvTOM z9P)?#K;8df!^Cb0vd67hc3}&00tkj|LqUK~l8?$6>U3s-$VVFrdB$|u!_k3*wjpV? zi=@Gwix7Q|T9^8r(jWvs+!--s(gXhJ?>;R#?GCq;q&L4+GZ`b-pH1qB&N^7AaZ z_I_8B3|7QI*KWvl$PA}+;}rX{1El0@zdi|yCV4pKx@g^}xrvKh6S1Onn<$zTY1&O$Af&7qQ%=P6b`By#FCo@<$ zI3b`D+xZK5|R9JAUnGd~+i7HkEPZg@~@Igcr|3p(0x>c8* z6sp$c*t`MtpU;K`yldu~$@W41F+neL98{#76$$o~kF;U;!jUwSwMu>y72SF|hwc0{ zMiP2tqc*#@fo;K<2>K!SP+a%Io(@wK30ZX`B8kdOoUX2}Vt(rN_$#`{GirNvS%kF_ z^;ePv>wcTv3eO$!hmVY;kE*VV8ZN}^Rv7gr4F&3T1@Uh+g{dkYAMS)InW>HMjJ0<* zbkF*aP#l}Ti9E_F0Ua2{m)j0_1x#|p)DW-q&31NMeN0&PSBQXOFwRDz<=b2W4qhcL zO0a5tAQrV&jB>{C^BLmq*I3fp2Ywe-jESfo#mM3Toyh-VsQ*oz|3H=h^7#L2tp8i2|9{oz|EJFXqRan#w*NAX z|L^wyzuW({*8iBs|8cPYEsFoH(*MHU|HdYz@o}@A=Y;FF6 zUm>R^p3R@tV}EjP%gBa&BO+&OjO?hiH9763g-n@kR5^n+vc_xu&RH^%Jyzpq&bot4 z%6-pSa*$EWuQ>w~S!I6l2|PXyqxZ04V`?U#u$^l-~ywboAVIG8U~EEm1&0<#qQ`+ zCQf>gcOs>(k0s)al4s8+^J`uf4|eMX`KrGGq?3=j8mYLZRTkVJ5tRJai_U#iPo6Tr~Z>_6GwJQ zdt%})4dIo;5mULL6+?JI&Z<()J>qswiE6efzWCm9aI=;Cy5Ty7ov>>+g>~2#rJuan zr-o|^bQ7hXywj%661THz!hd#2t+l{By3|&IeOmii{D4{m3)gW5)H2Ef1AiHfHxt(| zx71sROi*j|*GTR&pq`t@ojw@B{zzPr1%nX^VnZ&ChKD4IeMSj<+^uZH>{AU9=h$Yn zp6JxChCPU`)$+n%v<4(tbHpS)(tV7#9E{`p9C4_}1~ zgGuBbd>(GyDe-fE3z4Qy(wq%O>NrT($0S+K0VC8#NL{`Z^)wUR_)~~Sh<`Xm)=toM33_-C zOZyuQi`b(Klct5o@fK7n#0>oHT!_ai*Nw={wzC9h6Cb0ppYW%3M8B?D2=N^Lu=FE} zc52S8_Z&P?zWL_dZMPa*kWhhu@K|y1Q#dY* z-N!secm+ii@Cpvt;(rw^Vf%Oi0XReUtqInVeFcKdBkTMsK{k+eT@oZK$TojNg5(z2 zmLmz093sogJm=ha$S$jY&YA6B%5U(JlXYZOfnRenhisbidrmg24Bt%>%YXf diff --git a/flutter/macos/Runner.xcodeproj/project.pbxproj b/flutter/macos/Runner.xcodeproj/project.pbxproj index 066560203..18166c8ff 100644 --- a/flutter/macos/Runner.xcodeproj/project.pbxproj +++ b/flutter/macos/Runner.xcodeproj/project.pbxproj @@ -64,7 +64,7 @@ 295AD07E63F13855C270A0E0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* RustDesk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RustDesk.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* rustdesk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = rustdesk.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -127,7 +127,7 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* RustDesk.app */, + 33CC10ED2044A3C60003C045 /* rustdesk.app */, ); name = Products; sourceTree = ""; @@ -212,7 +212,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* RustDesk.app */; + productReference = 33CC10ED2044A3C60003C045 /* rustdesk.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -462,6 +462,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk; + PRODUCT_NAME = rustdesk; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -607,6 +608,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk; + PRODUCT_NAME = rustdesk; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; "SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h; @@ -644,6 +646,7 @@ /dev/null, ); PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk; + PRODUCT_NAME = rustdesk; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; "SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h; diff --git a/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index 7b4d860d6..682280dc5 100644 --- a/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { - "images": [ - { - "filename": "app_icon_16.png", - "idiom": "mac", - "scale": "1x", - "size": "16x16" + "info": { + "author": "icons_launcher", + "version": 1 }, - { - "filename": "app_icon_32.png", - "idiom": "mac", - "scale": "2x", - "size": "16x16" - }, - { - "filename": "app_icon_32.png", - "idiom": "mac", - "scale": "1x", - "size": "32x32" - }, - { - "filename": "app_icon_64.png", - "idiom": "mac", - "scale": "2x", - "size": "32x32" - }, - { - "filename": "app_icon_128.png", - "idiom": "mac", - "scale": "1x", - "size": "128x128" - }, - { - "filename": "app_icon_256.png", - "idiom": "mac", - "scale": "2x", - "size": "128x128" - }, - { - "filename": "app_icon_256.png", - "idiom": "mac", - "scale": "1x", - "size": "256x256" - }, - { - "filename": "app_icon_512.png", - "idiom": "mac", - "scale": "2x", - "size": "256x256" - }, - { - "filename": "app_icon_512.png", - "idiom": "mac", - "scale": "1x", - "size": "512x512" - }, - { - "filename": "app_icon_1024.png", - "idiom": "mac", - "scale": "2x", - "size": "512x512" - } - ], - "info": { - "author": "icons_launcher", - "version": 1 - } + "images": [ + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_16.png", + "scale": "1x" + }, + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "2x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "1x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_64.png", + "scale": "2x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_128.png", + "scale": "1x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "2x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "1x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "2x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "1x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_1024.png", + "scale": "2x" + } + ] } \ No newline at end of file diff --git a/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 1c6cf008ad8720ee4680a32de29836e8d31ed7fa..9af6f2121eb5a5394d671f8e0ab600cef240b3cb 100644 GIT binary patch literal 53345 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

    ^0>;>{-A-^Ls1s>XX&4yG45lUQ|yu+oo`W-dQ3 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 diff --git a/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index 8c2837a94be001dc0272ce64cfad36886903508f..493ec70768563bacad4356d5b0de3562d835663b 100644 GIT binary patch literal 5475 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 literal 2409 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 delta 322 zcmV-I0logx2hswN8Gi!+001a04^sdD0B=xCR7C&)00TUT0!5wxF>eAnfB_�tgZU zTFe4Nm;f(%812Jv`F>U~H|NsC026X>*vj3#b|2~xe8i4-}c>nSE z|Ig(A%HjWcwEy?||MdC)f4Bc=tN$~O|1gaI=<@&4=KsXr|9{5B^W*>k01$LiPE!DL zgl%j+1+;%}Xho!=Q&N{R^t}K80D?(GK~xyiZH`wKgdhw>$H86$V#TieU!w`4M~}Xr zOP))>&HTTXpQ3+h2NzTe UgN~k=m;e9(07*qoM6N<$f^iv?cK`qY diff --git a/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index ac1b107662f7d9065b4bac6dfe07e3159121e3e7..22893b8ea78c8c2d4dc1835a5e19b3c056e11d28 100644 GIT binary patch literal 10828 zcmch7^*f5GX0iY5@R1^d%60gM~hsysNN9 z-@JE_ktSk;`RobY-i#aWS?B+H3t)Jei<^*Qvvcx5kWveI-<8bbCm{F=vDm;Ck z7&3dV{7-987sxmOa{LWx4@OB{{WY!<7208rAcY>qt_Q|}3QY^9-E)qt1{`t$J$5T^ zkFK0lVHY3I(*du_YhF zCWT?#hcE-(Kksv)5L`o+pCgNpo-6{^QA#TOmrI^wontd&-iIoguk1E+wgSdpT`qc( zB6;JSl#+6Rkrw}YKrl**-v6s+T@0v_EzHd-B^9D#X12uUKXQ5<7`{_ae_x61V)%_& zPFU4Zo~n4H!=GC+DyX zrq9OR^{HF&<~{RSih@rb<;Hj)@cnM;Y`TWNg{r3JcrlR#3OR&6lJ-Za5GSC)(NR^C zZ}#ILvH4}W2q6`pV9J5DLX2UR&rX&LsxPhxUOyCm`}R%R=f|3~A@KK8^J2ya0v#OX z?H$L3Wk5_=xb;JPB%xh6{T)uy!xc{eatBJKPrIW zS%k97g$D^6!xcA$o*R9cpq6b~EQ)yrqY;_D{cP{HWk4e_ZLSF|p^+RX)e zK6zp>{L-=t_km)&Qp!>nICFj$nR=)R#7;nee}9AQOsWwE{m)+-3L0+!!|B}Zy0c`e zTkcdpntlU!@r#5YMwR&FTyXTXB#Dgi(XWWp<6yqAdu(j1TMlhj87b2wa8L7dGpQo@ zQP}3WAkg4D}g4JK&;kV ztmMYHP{CV9gHjJjHP(3}puBG*{4y!`w}=JqE~ z=H&s_10Vd5i5_Cfe25PN@ie|w`yDvW3;>V3eD!laSLrG5&!_Y+4eC}r=KPXhhWedu z#>c9hSWwPDb|o6YOXgjHe)S`Pie#ag3p_O?pfQv{L>3eW`<@|n`h$k_Em^!6$4$9hX4?I*l*&UX1(Hk+BO@bL zPEsvQ4IP~`{JaHvdT(i3;Lel#+1S^xfkc$~iwr$3zbTsX&_f2MrhkJG% zFZ+@{uJx~c13xK0s6GS7jVK~B<7Cemq$DLc@>zyQuD6v8sBxNx}rOsAg=SS_KGm|?%5#h!5F zFW$3c1~Fzt%+B=R80@JDzi$rs#&M>02J<<3W-`r5M)8q2MS?3lJu1SbkGU_Ff+zUL z?l*1YrZIozw6-i_AjZe+!nQU+vL z11`$vY2!&u#_1F05(0`4DGrF-mKNKQ5HXR2_pdeSjS1R0joE>sYEjH~{6UiA+wl>2Q)-XY`3hEP`(^mp0e^DHH3k>Z4{LPx-*Q!)QGf-+)Bb>jx z23j!p%5C)Map733!^rHz5z$az=ai75$CU{vCT)+L%V6a>Fsl7rEGR8sog@6J_Rk%c>hmdclbW>RtQ5Vl(#x!2c4#_sn`>u^669(ay8v?4l_0hlStisS&ASj&m& zowC!OQ&iXXhz}H1i5pVjxTYhWSnsY*>y1p7#c9=;Nd$KPLssSLQ2zdXuWDJH-g2L* zbv4J689BRyw>V%C<&{UmoOj; ze_suLB3ATpqiT?qP@KOx;^aw;mUe&Jys<}A`tX2peBuOEv;O>1RWmwPcDjSY2-vT! zg;#E>`Se9&%lhRyFOg2Yjr<9_^OLg-!?szZ23ML zV}g%yfs6v@Tu>ay2~M&>3u+z`t)^r+)g8l`uEb5z%4~x#IRi>pRvX%TnXR*OL&-oI zwx2S>g&0Pz6bK|Mqb>#Mm635BrgH+AJaLDsWf5|!-4=xSkniL3#4m3G2z+Gqtzz2w z8_=M;kk-k5HPFIwg;_R`KnAJ{iq0*Od{hxTIjV>PcUz==Vur~s*=jdDjve9$BX-PZ z=UBDGp)Sw?GM*VrWxv6ap%IcOR=3p^`s{-rE6reie28M&<&SA^gAmy#SDy^g)|&oY z*q3~MXS?nQyh9!McYI^zZ=N}WTH$)JxUIRqd49LKZUmHQ?F2qHR2E)iDud#gej#HDxWT%lLcW5m9g5<}DXC}V&Zwi2Rkgr8`r(KxULMOjj{{3R5kA>@zA8l71a16>fFT@VwN8zA^P;3S8eT z96BlTSwIP-f3Dg?Kj4WPMhbyu4aobxUmDSzgc4;K1-JXr5fz>@fQ)cFR%m8k#iJB;AwS3w_NT`{lW0k1H`x*q&rQ%g03vZkho z!bO|yKsAF(ePkFZyfL7q?SP$94P}cQ9=?vh5~4zP^F3{lB{nDIXPI;Omufn1Qxn4b z&ypwMkJeT=w|f|hCCe`Ej$!LFl=jh_VE0RLf>}M+OYb|ehb4=@5?4R!o&|FA;I>lz z;fWK1OvhfgR<3@D{MU9;N6&e2t%P^JdMK+9+}w(xg*p?>PhBC>FpD9aSxF9`zS`EY z63t#~F3o#tYg@ijBmJqSmA-Ht6SX=Hp}VT6--(vHcNJiQK_(KgH;yU+$kh2VWt&3U@ty5dm=7;@9J=Nl#!E*I3iHZcxRPZd@gO$xgok#6X%6( zH6*v#ex1MZfbvmL!=j`7LyIeXojdw{97ODU(CVW4I?VFf@LG>ZF%!fe<%!L@T3)Qp z`?2adr0iYSSAGj;b%MS~S5uS=ZZhgm zOXH4VKgtMPV% zgJ*)yoQNJOItjQLM-62~pB7$|0{GDj?;*Ewd{0u$?B!$UDbvegHR-mQPnY!W)d2FY z$wX!!Lr*twD4cyy%XzuHFCJlh zYX^g(!`*(r<*1f`AdIkNxUMv)!IQLp|GSQtx{s}ZHr6fM70(TX>O$7miS{~oUQ$^5=|>k&qcPmn7{+PTcDf1lMfHnw$In{TJAbAqPhfB7rd+zM}Ocztr!$zOsx-TUICd`H7&)?nXtvnb_Lr{+**VZTy1Hep3W|$TW`gZ}-tN zIw{tL{aP!kgvtnU^+?o2LcvDyW$yKzikN%H4KrbIF)sFF`?I^?^%^bbszv?hl|5r0 ztP9ms1YFe)GqbK%A^}AdrMU=IrldFBXHq=M10_od*FRl)4ztQumXDfkbFrm_c;>M79m;g=X*GI2T6b52uB;Y;Syu)#gjPy^H)B8}?Y_`=ZBfE!1Ur zhF)AJK7Z3Dl0rw5R$*$;t_fR{tgEFe9z-OS!HURNSNkC(lOy16jqM{J_88jz#p!j? z?T?Ih1HPS7EAN)B#G4r$(KmoZ3SStiwzK;a8^Y-W%L?oIwN8HWeieSv%E7zmF<9O2 zl}yy1SK9(-5!?fBuD0z<+@)%f3%A+0B$Qc|t3ShZ^8Q&tRwGPBa{3C#;x3Fi5hCh; zil^wIt}*irB|eAI5(pP5@P!E{p5L+ zLFf=0H^4&>?rJ=&9a0wo4i=Q8+Pc)e*=l4~Pn~MgUaHdpaiSrIwe^!mE27+sAZV)~ zL1sFcRX7D{_*ZCAAo=!Qp7GDgt#2#kflzSu=E!|Q&;zRw(7Dn9nfb(f*vJ9capl0< z1#&xb^H5wVbYNFp-Mc0z?_dBw?ud)c$$)s$rt1OLkfF^|bt`os`a#177T6}#CPdHp z<7WKbHdexPpYb723V@tLf?aVg>&C+mCxFw4T6Jx-~42d_fXj=>MIEDqF!+wO~3@Pjd-vop}!y9G!CDv^HLyi4CfwJ%mbTA znT>(!Qsg=AnlBplGwV!1`b5e|LrJrVVP}6PKh!yb9q#33YGNfM{}x8j2~V9RY|rYEBDbHM?}ny;47#PP zH4(llpyB|3-cqT{`!ANxdM{YK)%&nnXv{>H+r7}nuWJC0->)+h(+Q)1*E4)6;GImL#USjRx>;sFr;B* zMp@{F_bmUWPOWMs3#JWFm(MdWTKS?3GfKb?n!LVp6AIGq$V|zN3ds=haagJGTc*U_ zufQ!3rhJoS*-Wgh2FOm>9p_EeSjkTd&Ac8MTphko$p*v1ZI;8%u8pfOGIL7*RKhkK zk1nj*{(+>lK^a7wJyM1y^Q^d1^VEKp7`V=o(}N)1Y}$?SH%UB*4iy5K>&0$Cc{`Q@ zg`&t4dGRS+!Q~(WhE!7IuL=M-g;|3)De-~-DXCl8nd0|l!Wmz*S?3{>=VA zrvqH-q1In-r2KYY|-#_^NQ07{?`V%hD(0ZfBia@npLKH!`W_cO|qG z>|V$5)hZlBpHq7LSxOiN`}_f?br;(^@_ya8kngdvoC@FlSFY%DYU7F#x!;ei$ud22g(h6PhsjTfV8LtPn~o$ zj9cN$dlEFL9PnZ$pD7FoWQDFr9N{i_waz>d`Mw`MAV zQ?d$6=s{%@jj%M{0@zaLVqsw?qpRf?{k)tTTjvHQ3I&>hoA{+oBgH05Py z<`vuNhbK?|T`?t<8qh=8f!06r0y4M|j6#c`I{Xj2M(ye)&vC7pD}>}s*nwAC-&(L8 z;y6%2x?F&E4Xw#PK`*{!geDpTHpfN$ALVEiVPi_eq|3BB%;^F` zpsgf>g~i>LcED*;WF6fbKvV;-g`$n$5e!EpL96QLmf+GW@&=MxYuqQ=_RIxrLmO}d zpOR3_6lT;PNjjX=Xv(_}&!BzrJl|Eg=rt}?Az2ahu zT0*7>AqC4#tL&-Ky^(+q^?(sOmE}KpdI{h@A-38hf>o?TEX7rNZ0dP-0fUza%};!n zY#Ly+sy3b?)*xpFME3y=bsR2;&i5Y;L8O3MAo{(h*}r$BsT(+kW4O5hoKN*1 z?}I{QPVt44keG8^fZ-O1NZU-Sbe6znd=*!%h6v2OG(p;Mk)SS&)n`hiw$pChU~DT$ z5+C`d?^EcAHLdQdUO^yk)J9Lb$REQdTGrg{19ZI z0BUS1Cj#oZD15+nki7L*Sc)aoPgf8IAFo#~p&Iex{HkaFhRQT1gX(&`+>d!16-ZJ# z9n;%WK*S@@YmxzHgyb#PX>yA#XFH<|fli=A%*+S_&&Muq?@^vvm{`lzo$?J6)fvGT z1w)|8JG~LoY{iv`11^AoU&_rxyLw~Cg?Sg)_QDou@C%hk-SyjeV8AAs!Wn`%uxax! zPkM#nVLsU3U#}1{`vw#~Awl`E)S0r96VQ|Mlv+}9-l%6N8Je`dEST>6xp%y6kci8p zx_o2L6N_omczXX4Kb0J*TO3;IR)mk~s%AsArrrEv_S4!4fNqgR?V3`=edeZn=Wc6k zr}o0sGL0PB_nbiHtM~rX$uRWVRsQfk9lhI)#fseEgHryZhFc20=v`EE(*u!Sw>}DyM01+>W=G}a# zV#LH!{D>TTd1eV20}G}$CxB70Yl9{97}$q#DM;1Uaq;Pj*b@-39mdPS>0=)y2~0Y3 zn^xQR*iyMe3L%WIv}nHTKk80;k?LXG?%N&unt~VwIw_Y;4>r4@en1@8R}@TuKEoB$ zfFpf*NB9LbPe+Uh=E3{S!+_DOtWByA;T9UN;TboYtj~*}f-kE$z!BQ2RqT)%O*4FD zX7^m22dB=VON@i}YTZZNZqsWAjP*|UbIKsgjoR#!DZ2Z_H5_1xmhyh~N5t)nyjdL- zK_VY;7y`zK0t&p^v2T4qdM|%PV94IQ69r@T5u{kWOnu^pP zfTPZY=m>+uulwbt4ws*`{#q_Uw8NPT#(!P@QWIKPn8!eu9R|u*ZvtsKg^>jyxBE_W znqrGr*xjplf#LL*!IXv@<$s~+oQPp;Cad?~wL^^8a@^kB*^8k@ew3XE$bdu=CN;bo zKVaWIVUq2>Ct5yVZ(bW*UWKN!BHFuFa*DgrK|kf3ey>om_nnQvlW)P30d7Mxb#dX# z8h!SUVIq5|yPDaB00!G-Gm^|dtWPN}^d6_oxe`c1VXG=D{oJZf7c|TWzjX;X$MZYA zKn!2^3YPa(=2g7`3dZdX>ibrLnMASaaV+I=F7J*1llJOD|+30f*Kz~ zR&UQ+x?2h>BUpHul19@)DQ~-Zq+T!8x+$Z7%X;Rh4|6`R&p3_S>@7C)OIBCpQ9P|G5h_Gz(vvdaB*usMJPa%`-UjNhKd+Fq z6aX3jLH2F+#Jv`{9{^ij4pbP=s1+*qJ7kFt&#m`srIXB;uvW&`qU|3#PCurdi|HK@ zTK2QX8#M4l*;910k7*czn)B1*crHAa9JbIeb_nhcIRfTPz>jA|0KyO09O6qN zf4$!fd~Xh0b+P6l$RN6e{UIj$H9Ve_1zYlcR{w3?8mEeV_OX>UDufnCIN#AlVDLqV zjv`2=-Ov3Iyrp{MKrn-YbbTKlVhE9#r6`Lno?1xQ`G)E7C@#$ufQY^qQYmu@G$)e3 zn5u{lRQl$I!i^)Kl6+@sq(|cdSi0=Ydzi|H5V0mD78MhyaD1nOlTb$pD z@teGh6(J7=t6@J@4&s|q7ToObsgmaR0oHErUnqu`OSSo8@v>>l22E7_ z>))3@e!3Q9iK+>^IkfQ>Bh>v+ml(E852fi@y6&iafuK)R^N^dA!ox>eN*1nkZxi(> zHb|XVQYcg!hwu;^&-E)?LXPd@>8`QILnbXpR^#q#i1I4A5coGtuyN6?yi+#<^3aID zcU&ZQtF|E$WK>O}Mq)~!*V=-2^$?6`Li%$zTzupUw9Qnb!Rev2OpwF<`++d8Cn~rgYZ5N?iL(o@d)SgmhOG|ga&3%0xK!xS`oqD8E+(q-E9qwQXQsmd$H-)`+ zw~g5NNLe-Pkd`?&TRAM{0{rW2J_s;T#pB1#gJ}dNj3hs`AKc9>?Rc@JoCM{<3TeKh zGrO~iJT1QorsD#TUKxNNNg19yG|r8YY-=q&xpVF1e+{?M6Z@L7NP7q31pod@NgI#z zu#>naYD9~RH5@**Kc;UC!Vr*vuPak+XP5Owb-Vh2^l+eyJ3UmFnm5mz=t~(xM6bpX zLxcaRT!V)^dJ#Bv0uU|k~GBZRObp5&$zHuU`dvnLlE+P(`#MCe`31mVS#jEFb28;M`BpzJ< zDYc_zVU1whP5ANmQiq|0i`ZFoS{*H`si|q{L(PA^U64T9xDB(h#Wi3?WGgVOtvay3 zHDCPyiEz-YwuT1Ghks($m;dYJDms_Us|_+sOjuS^`jKn(aqQ}2Ow|9X)6)7ny)S5; zLM&b}LXwo#Y*tDA0C(VcIpp!lu$+z4a>wr9D2I2nS{w?<`-A10%f3`Aey;^JLi8m}VT zy!QyRa-()Y71h8qU3|$!`(F+)uB%mg912sMa+APX1pXW=!oMM|9xcs-FO+pcVC;Rh~z0}D(ViG zzgm##i_vv3WDng^0(3*QKZ#|TQ6Z0N$5FXFyH`8INagW{RxX5E=jB9?*`kQh)9X&! zzl&~4is)X^hAaTbiB#e$bcO9@J%{RCl=gd`U)aL?GafwW+hz}7E(Br6<(df(xoJ{V zC58#vm4$4aMs=!DT3)GkSxig}o#{9GWG67O&37olV+YkN97<&Vip>bzqv%`@&N3q( z{mvQb=P^?PR`IY{Q7EMJ>l-@bB12*jNz{F=t8>p7+oG|xY4foNOdyvut}8bgIC{X~ zx&>5_C`+~&fT*I8@r<2pIZ2@n4YbSR&Kc+tbDuGWH1Mk`V`>;0vfqew=ShV;SfMMW zB9UjmQAp>NjEo)l1LI1)FM^1uMHyrj;Lsu@1r&)xz2JOxllV72ir^`uUYWzSF6k7e zfjsDn?|`sL%%wd!uOx!?X|&7UQ*@N%2tQ<`#qJa16~@sjmI{Gl@n(R`o*$py!jTps zSOM(9G1Y2Q?|`Z4>B18fof%DJdgU&5^5@g2DjuLR&m^iRn3l(N`gn#Lc}SWZQcwZL z@}1!gXuVX39RJswK5G_x<~Kx7)xjPn!}qJK4Y-aYcN$ zOH-_U(VF%6Tx3=Lu0bmbfuNdh3ewmDqfzgE> zR1lo-68vA4KcQNpA`~a5p|Y0i<*SDaOlpucY|?^uPg$s%XT1K{eNs1S)jbsgJQn1^ z&zxEg_uSbImQKK4uVnuC5B13(i#Cg`nD3h_}V zU{q2mzO~=Z``$xG%70qOPxX_!Y!lHmhGj|i zb5(=Py*e`M(?i)FDaRfn5_Yf7{B*EnZ1^o!U?mj2`Q5h(b^f3v61s1W zrA+#ST?t}CS6hoW#|Wm#i1&8I9yS^D7(pT*+laQJbYtNoe_gNS{!;Kk1V|@tm#6O7 z%UqGZgYOfEf8qBUVA7r)KJzyBku1T7*1>whSWiw*StoqZOb*_-IgRE^56vx`Eg2X- z5T}C79s8cw&$Z1U%7dPnnYlK$?WH)qrZZzGn!JO|dF5GS+U|4Um_H_Bi0174u;<|5 zkRL`88N?eXwYY**=Z6?f#42Fr3)| zh^rp7-L(bjNSQNu_Wm8Oirv%VdrRb)TF^uKtR4V#+#63La(H1goF47aON3y>=W|@) z;o!JA48z2CmTK<*1B!Yh_3z^p$raRuZ{ze@e&`ob?>EfzOR1M!Tz6TtEZ1IOW-z3H z-iuTTM)JuUSEvfFP-BTopSa}aTHLX6$rMdH;S?93m8*o1oR*cV=LgSp_?>CgG?aJ|Ka#IyvyLzAW`qRPl!loi1;)?3jY%$8KO5njCh z@_)|nj*ChF;5BS>G^wCriusC^?0XoR+psA2g7T%fdE99s^w{K6mfi-VzY>bvjwfcJa{dn>Q?KG#pwtGvzfGYhXA^Z(YV!3BH zjW~KSE@(q;uyr$Mv|KI!_q}T`78x&2sQE;W^d*C$Y|u6H zTZe!YpMEToH-ynJL2pv|ajP0iXvskES$WiMV<%&?EsCXaql2=853lQnS_kY$r((nB{ WX8Oy%I)Y}20+i&`WNY5Q!u}tVQGC(> literal 4616 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 b*WWN4QtqF=Dsb+fjK7(Yff<&phmZOnXjx(N diff --git a/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9e593fcbd46695dca489df197dcde05b64211cce..583a485712bcc8691458c7abb38b37906ae6649a 100644 GIT binary patch delta 1549 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 literal 9733 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 diff --git a/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 76d846c4a66adbeedbed97062f890cbc5832570e..f98ccf1f3eca7f764f5a066ae22f6b88fcf6653f 100644 GIT binary patch literal 2851 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 bn!n8-#h{tR4FTb;00000NkvXXu0mjfO=(GY diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index ebb105178..cd618dfc4 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -488,6 +488,14 @@ packages: url: "https://github.com/Kingtous/flutter_improved_scrolling" source: git version: "0.0.3" + flutter_launcher_icons: + dependency: "direct main" + description: + name: flutter_launcher_icons + sha256: ce0e501cfc258907842238e4ca605e74b7fd1cdf04b3b43e86c43f3e40a1592c + url: "https://pub.dev" + source: hosted + version: "0.11.0" flutter_lints: dependency: "direct dev" description: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 95449e611..8701d9f5b 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -90,6 +90,7 @@ dependencies: bot_toast: ^4.0.3 win32: any password_strength: ^0.2.0 + flutter_launcher_icons: ^0.11.0 dev_dependencies: @@ -101,21 +102,21 @@ dev_dependencies: flutter_lints: ^2.0.0 ffigen: ^7.2.4 -# rerun: flutter pub run flutter_launcher_icons:main -icons_launcher: +# rerun: flutter pub run flutter_launcher_icons +flutter_icons: image_path: "../res/icon.png" - platforms: - android: - enable: true - ios: - enable: true - windows: - enable: true - macos: - enable: true - image_path: "../res/mac-icon.png" - linux: - enable: true + remove_alpha_ios: true + android: true + ios: true + windows: + generate: true + macos: + image_path: "../res/mac-icon.png" + generate: true + linux: true + web: + generate: true + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/flutter/web/icons/Icon-192.png b/flutter/web/icons/Icon-192.png index 5d4566850a8c402c3ca0565f20295e9466361d9e..e8c754f4ad2127a599473544ca40d6169fbe35f0 100644 GIT binary patch literal 8908 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%0D7IE-;TOZw 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 bECTt^{N{VZG)-Gz(;!_fgDbU{>?8jN?wrh+ literal 12570 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 G68;CvQ0u<{ diff --git a/flutter/web/icons/Icon-maskable-192.png b/flutter/web/icons/Icon-maskable-192.png index 30147e96ef31e6bf72170d69837eec2f42d4b8d4..e8c754f4ad2127a599473544ca40d6169fbe35f0 100644 GIT binary patch literal 8908 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 IM6N<$f~u99dH?_b diff --git a/flutter/web/icons/Icon-maskable-512.png b/flutter/web/icons/Icon-maskable-512.png index e84ca5bc7a212951765d3993264a50517b8fe82f..2f8929e267b44bfe9325ba479ff131d16e2586de 100644 GIT binary patch literal 25973 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%0D7IE-;TOZw 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 bECTt^{N{VZG)-Gz(;!_fgDbU{>?8jN?wrh+ literal 12626 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 literal 21592 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 diff --git a/res/128x128.png b/res/128x128.png new file mode 120000 index 000000000..f69b60eb2 --- /dev/null +++ b/res/128x128.png @@ -0,0 +1 @@ +../flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png \ No newline at end of file diff --git a/res/128x128@2x.png b/res/128x128@2x.png index d6f8d20fa045eee8541c3912b49428b68af98324..9bccf65bbc430a5661e689143c553bd2e223dee6 100644 GIT binary patch literal 10623 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 C;{ApI diff --git a/res/32x32.png b/res/32x32.png deleted file mode 100644 index 33dc805376406f8e5ee596dc1484350b1eeffe8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 493 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy69RlfT!A!0;eT{g@L#6%|Kzp* zFW>op=)(VwrT-s1`@iet|AgNEO^g1&`S2eIzW(^nko$k{ng4(O{y%>Cze&~q<$L}I zwf&#I;s1|c|37~H|MJ~`*T(;wkNlS}`~Udm|2dog*UbArW5a))ivJ6@{eSxU|GNGE zBRc;V&G_%z^8f6O|8k}O`&a!}yk`9n=p^=%AirP+l~wQmzg^ir;ie)310$=ai(^Oy zGMa`n|T{tWZ_4Bqe#( zj91f_ykC6Pi_=9iGSJl1+-J3!tinyc9rO4dC3+hz7O*@}ah|}GvTwo@^Pf z=Uf+t*$+fFOtw0}w$8MI<-?nA52rZFh3d|5urlI5fBpV<69)5FjqThq_tX{N`8Rw{ z(EY-YpnjCW#y#xwT*jHp8C%v1D(ILheD~3~7xZ)0_NPm3Ulcp}Q}65cJJ$=;qYj;v hTh_$uerzE>1B1vdse~7o*t$XS=IQF^vd$@?2>_Jq`C|Y8 diff --git a/res/32x32.png b/res/32x32.png new file mode 120000 index 000000000..7c1136a73 --- /dev/null +++ b/res/32x32.png @@ -0,0 +1 @@ +../flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png \ No newline at end of file diff --git a/res/64x64.png b/res/64x64.png deleted file mode 100644 index d93638e6eb1d82b24807d7b3d0a6ec5c72e0bd86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2264 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 GA^#74rn9U7 diff --git a/res/icon.ico b/res/icon.ico new file mode 120000 index 000000000..75324b38c --- /dev/null +++ b/res/icon.ico @@ -0,0 +1 @@ +../flutter/windows/runner/resources/app_icon.ico \ No newline at end of file diff --git a/res/icon.png b/res/icon.png index 823967c49a8f3440fbb26e2bd05c96c78b939141..2575d80e7bb285054ef964780d77b4a9a90d1491 100644 GIT binary patch literal 60426 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 diff --git a/res/logo-header.svg b/res/logo-header.svg index 40c19c43c..9712636bf 100644 --- a/res/logo-header.svg +++ b/res/logo-header.svg @@ -1 +1 @@ -RUSTDESKYour remote desktop \ No newline at end of file + \ No newline at end of file diff --git a/res/mac-icon.png b/res/mac-icon.png index a9813152ef248d07076f956a3642575c396c0811..b6e08923fd8fde70dd059b76976ddc32c35749db 100644 GIT binary patch literal 51695 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%0D}t}!A64# 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 Te#RJt2VrvD;u!IW+pYft1QtW! literal 90116 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 z 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 literal 809 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sivoN?T!Hi;2MYvMt$SQP;xz=dFx){*RWnv{mVKkiQ?V zdVcyc-^*+j$Ib_rS82OFyzp9feZq3v{D;kfhVv2+>fJLr{ag7;#nI{AvrgVvIbFS2 zC$N>J^Ii4ZFM>w=?Cg?T*RVxunlQXQ@TF!#&B9JCAKAoPszr4oCEU?1O)uML`iiUw zPONeEJoW1!3-6u3k+bz@cB>^9Nhm%(>h{7{M)5M2W3iK3^{Hde7EF-HoPBuniapDs zdU9&*Q$5yZ`%ke=vu9-5xmqh*#cARtIgQceD8YNW`njxgN@xNA D=o26P diff --git a/res/mac-tray-dark.png b/res/mac-tray-dark.png index ba8ed8c12cf19a82e8109f4d7b46e10b9afcff99..a98fe63b0930e9e9358059dd6661e1eaadfd73fe 100644 GIT binary patch delta 521 zcmV+k0`~or0+$4k8Gi-<001BJ|6u?C010qNS#tmY2^9bU2^9epf*r&F000?uMObuG zZ)S9NVRB^vcXxL#X>MzCV_|S*E^l&Yo9;Xs0004`Nkl*YzRL{|`7)pTLb9a9uA|C+U{jP{?nc1_d zs;V?igR0&v%YX8nWLefTv#(Lx$7c3;JRYAdYaxWNWoAb}$KAVXW;+plz6u6tn&tq= z09Dl;1@1-p*Q?-}zD4K0a`9En?)qsBjH-T){F@3$l=pv;MY;QeU%40(07T^Wx&pwR z$p1ru_faxmMTQV^;Azy&K+S9)FyL%9!b_E9*>y8}3x8ahg-lgniMx}g>4PLmPK%=W z8A8aLrgx?$?0dB2u{fplO1xTM*()12p#&C!%B_r;BAbp-{@684td9^#&Z%%wh1F4GQTUQP1&+u7*cRb27$&an8u6*ezg~ zZwgT~*TP5^d^Jok+S>?jbs+4y;UMVdxM%*GW4hn&*ry9n1R+;vOED-50ssI207*qo IM6N<$f_evhlK=n! diff --git a/res/mac-tray-light-x2.png b/res/mac-tray-light-x2.png index f723d980e594a0042a9a94aac6aef9127ec3bf8c..253450ecbc102a345187896f2b65ab1900d8fcac 100644 GIT binary patch delta 1184 zcmV;R1Yi5A2B`^<8Gi-<00374`G)`i010qNS#tmY8$kd78$kiGsWprM000?uMObuG zZ)S9NVRB^vcXxL#X>MzCV_|S*E^l&Yo9;Xs000CwNklZK`teW5`?A@Y>|d7 zW>hM)X;SYUCncSKi|1VDdS>o9&->nUryrcnJ^%B*|L1+*^L{+{v`L}_ZUr6$`hk@| z53m^MNbgsHzkng&81Mt|9q@az6}L4JKo{^d@FZruEuxGgz+T`(U?j?gq|j@jq}L=> zRUTuK-jcK=)PGNic&4NmOE#Y6qNL|T0`Lx@6~HIJT~XZ}&RXC{k1%m)Heh3Qy37MC z^9qZ4U@PzrFtbTRAq)bmfKiV)uLrgR?=!vTPXp_5nuo?c@Hp@}Fryq_E=~gvVLjkx zssL`oUZ9T2@*u{6VXT+ulHV9n=7CAm=qyP`Bd5SINq-+n+9Ih}Qj#fqnWT-9-j!6T zdu}Mz=S`Pg>9cFH_p-uQB8*ohy(DQ)frVXeQXg~pMzO44vn z{S`@_l7FtP16zTco$vhtU`Oz5;inro2+YsXbO4)SVja93m9IoJX$aOgIH z1+WCT+gbQ-;I}wmZq7P&_W|=0U>&~0$QZCE(o_gvq;(aM0P7Oq9%sS(fOD~~0;pg? zc<4{CGpvV$aekus#;IGG0KHD};Yc$v3^;YYZGVy~6mQ(F05<@oq%a)Z2>j#FjU>QL z4&gAn5~k)FiP3@0O7wusrlr@#{d&wK!!2d>LXERb{| z6X~F&b3%4->aR{0Te1U#a~N z`zPOO;Oo>_9<}U68CmWsX8uI|k1@G_uX!jhJwJ~9BYGlxBeWYs%$Lbd;LE9Qgnta7 zg}~4Fl1^oD6K8}c8Qimf_#n!3pV0000rPkH?m}pC4S`E^%>RVX83C>l269ubw}>Eh{EOljH8WiLK=cY6?qh?G5?4 zdUO1`oXWiCi7_xRF?qT;hIkzBopyWLDg&N2XGg)0I$B*y71t!^9==!kKU~_H$8+(8 zr{$rW{+Z;?TEo!qlKIB(e3j^zz3(2fl+F6gc2DxfS02;bfpPlzyc-T&j@J|OY4h7! zV{=?+sj5fGJ?-uM6L(r$oLKR+c*0aCW(}Dcc`TFkO#*LS5nJ{);$44J-?mnb8Fe#m zx-?p4J(l)Ly7pZyN}xgF7Twf^~iz^mV3Ir zv{xNHaqYWv+m&6PTyG|SlRhwgdsSMmi$c(?Rw+G|n|Jq%l`P=NdSU8ymhHg882ts) zSiJo{%)h4Vy?DZl6RZ!KS+!dIXMFC|DX}#CTU5!wJwN7n$#W4Mo{78W8f|0aR@QPj zFkPna$pJw(ec4Q=xSci3+^2&?i>5tGP<&R^8((EHKR&x(^?FsU!=mQEyZ>{-A2jU_ zoOLI9uH7e(#}cVc-YXK@)6#MzCV_|S*E^l&Yo9;Xs0003jNkl#UYIJ7F_P{lv99e)&;`Z@GmJYH&Ve-KDQ z@=J1Zf=Wi_)m`;Q{ZPNuJ9SU3W#eOGJOuUpAK&WQAcCTLG9)p@qdFNA6zF3csPl_1 za0HwOdl@JLTfi$Y1=N7!bmqA2Yqd1sZBi+#&mm`)Fn5agoMhCqD87*ZD^cr*z$3$P zAL;c3C`FEooPT71%SbOJz*yvP1cE>D3DAigiz5&$Mfy_$oJNjHPI3j|bip%rV7MRU2wc{ZzX_-%;nX@js(a3259AKP&(M00{s|MNUMnLSTaLAEsOY delta 253 zcmV+_A(1OFPEseh85xH83Ft7_ zLY;umhDDCiUIDf`0C;A2l<4NTr~a8^x?gVFS2&vj^r{wZl8^AF00000NkvXXu0mjf D1{!dZ diff --git a/res/tray-icon.ico b/res/tray-icon.ico index fd2e61628a2ae248de18c5492c90badad1025aa2..df8bdaccbd9df0c34ca00bb736ba2361f94e1705 100644 GIT binary patch literal 4286 zcmchb`)^c56vwA6G0_-bAi-*46MljCqpxUu(4e3ajs6uz{lUZ-P;4yKVnMVKCB)D+ z_z08`H3$_bExX$^fFRN;E%>CSw7c7FugmV;*L{9HbGLUpclXY27t_w>v@>(gna`a$ zvvcMyV>R??Xkh#=J8&Cg%Nb*sW_A>H6DdC7M$2Edi)sU9FvNfxT`u}_k0 zAaS|;e;dhSmsSFD_6v&*7I=Iefo=k_t+`ed)C3)$Y8$3U~DF7V$hozv=)Zk`wZEw44F2P?KQ|9SOY71 z&P(!dCH%XE-pB~-jdk;6Go*Jgq}mzOowdk@UIR=`80 zcx#>a!{~n##`(`kc0xP(Ix=6ZhAPb4Wyd=h{_Uc5IP;2UJZa;fGZgP7uv+VZ*k!L- zMxp)vr?*dN8RI9A+P4bDY-KNl@>MO2=^q7a|Ek@wer~U|t$4 za)=>v>{h9xzV+sk^W6OPfAIg>4Uru_%n&|ylbqjvcTp~(95eWi)V`8Jy|~F)b3Sj* zp6sC*gjP!3^!@9FJeEIGGy5Nw+E-F2!G|ESCyp{qpMP5FW(>SLn?t?lxfwkx^^qye z^jHb|YA@xav28LJ*wdqwzrAdp$I=HMp!|`Q`uQm=O+obdoz9x`d2{yFK8k1RlAn2@ zFWdciK7SYGl63D&u&aCqJu!rE-_im*HvCM1Bdb{9)0D?*3i-Q`OZG4%zNfqwljjgC zGXdN1UTO5?5GF&*FdnFbmXQ03&yx+j?A#qhHqW8%d4BEagGdd(jqKlD$Xw}!I?@Cs z^oSGhsT1`u)KPIbSb7Xbx{7OQURusc{$q^z79MVDUyXMeC!|zf9G|$A;#*q O@Sg}4xo~EP*#80bVj{T! literal 4286 zcmc&&T}V_x6rQcZM%El76HzThvO6B<#UW z`%x3pD2fc%wZup(BTQ06=^-+~ptZtE{hG7G=$$+FuCvOzv&@}4=g#+i=bV|HyR(dW z_$MKO(Uq0WVQc|oYz42}#1eTWd${mtn>?Zl!^jn0mc(m-*FIhmqe|R0NMYMXlo{hT zUyStJ<49o_$Oojd?Gvso^UL$VwQJ+1PjeB?d}k~K%J&0xdBCOBBDbgJiGGzA@GW=G zw~SeBe;CD{wG?=J4;UK6aCviEXjL2U)>zL4@EoFd|TN+zXqe=xGDeXPI*}m8f{?U0!2`FG$p}<@?OAGM3B)z8-kqt>x(%GY25XS zp}pN&zPvZX+4UO=tt>!iy&2E_11hdR1f)$j!&-fyd|Qj$udQR8?&;lOFSHjrjdin~ zo(~NEQ(2y!Gw1mBNyXCKrA;%(*X65t>gBID$3Hl%;~Q_8?SI8P^>@M77ISUNDaG2` zrTftO1h}}&nd`(%pgL2}JFygQb;h{OAK9;MaOR@@i1yd>iv?%;Y=GWzzrXALl7;dX zNhi{?(!F{vrAkw=;Jv#?TJ*N98aO^rVD0uDPS2>dQ+wp44+|ZnV zs8ru6&1FKr;-FL%ZEq(~lsvwerLMirUMbdCR|9Zkv(TX5rX4{(&wg&(!hdf~idH+8 z=dICv3GuZV9P|3L=O_ApfS(1E Date: Tue, 7 Feb 2023 21:14:01 +0800 Subject: [PATCH 097/117] add design.svg --- res/design.svg | 374 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 res/design.svg diff --git a/res/design.svg b/res/design.svg new file mode 100644 index 000000000..62e568242 --- /dev/null +++ b/res/design.svg @@ -0,0 +1,374 @@ + +rustdesk From 7820c890c569214fffbebc9382fd7adf35389cf1 Mon Sep 17 00:00:00 2001 From: Colin Delahunty <72827203+colin99d@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:32:42 -0500 Subject: [PATCH 098/117] Small fix to README wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc9bacf19..62950846f 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ RustDesk welcomes contribution from everyone. See [`docs/CONTRIBUTING.md`](docs/ ## Free Public Servers -Below are the servers you are using for free, it may change along the time. If you are not close to one of these, your network may be slow. +Below are the servers you are using for free, it may change over time. If you are not close to one of these, your network may be slow. | Location | Vendor | Specification | | --------- | ------------- | ------------------ | | Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM | From 9527d3b41d9408ea377084b1c5e3a018f0289f65 Mon Sep 17 00:00:00 2001 From: Colin Delahunty <72827203+colin99d@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:33:38 -0500 Subject: [PATCH 099/117] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62950846f..866063726 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ RustDesk welcomes contribution from everyone. See [`docs/CONTRIBUTING.md`](docs/ ## Free Public Servers -Below are the servers you are using for free, it may change over time. If you are not close to one of these, your network may be slow. +Below are the servers you are using for free, they may change over time. If you are not close to one of these, your network may be slow. | Location | Vendor | Specification | | --------- | ------------- | ------------------ | | Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM | From cf121bdf47550df9725b604e340e1fa4491cafd4 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 2 Feb 2023 22:27:11 +0800 Subject: [PATCH 100/117] win, translate mode, not debug yet Signed-off-by: fufesou --- Cargo.lock | 26 ++++++++- Cargo.toml | 2 +- src/keyboard.rs | 105 ++++++++++++++++++++++++++++-------- src/server/input_service.rs | 17 +++++- src/ui_session_interface.rs | 21 ++++++++ 5 files changed, 144 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e15641363..4ac2720be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,7 @@ dependencies = [ "log", "objc", "pkg-config", - "rdev", + "rdev 0.5.0-2 (git+https://github.com/fufesou/rdev)", "serde 1.0.149", "serde_derive", "tfc", @@ -4401,6 +4401,28 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rdev" +version = "0.5.0-2" +dependencies = [ + "cocoa", + "core-foundation 0.9.3", + "core-foundation-sys 0.8.3", + "core-graphics 0.22.3", + "enum-map", + "epoll", + "inotify", + "lazy_static", + "libc", + "log", + "mio 0.8.5", + "strum 0.24.1", + "strum_macros 0.24.3", + "widestring 1.0.2", + "winapi 0.3.9", + "x11 2.20.1", +] + [[package]] name = "rdev" version = "0.5.0-2" @@ -4709,7 +4731,7 @@ dependencies = [ "objc", "objc_id", "parity-tokio-ipc", - "rdev", + "rdev 0.5.0-2", "repng", "reqwest", "rpassword 7.2.0", diff --git a/Cargo.toml b/Cargo.toml index 936b9e349..5d75b7a23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ default-net = { git = "https://github.com/Kingtous/default-net" } wol-rs = "0.9.1" flutter_rust_bridge = { version = "1.61.1", optional = true } errno = "0.2.8" -rdev = { git = "https://github.com/fufesou/rdev" } +rdev = { path = "../rdev" } url = { version = "2.1", features = ["serde"] } reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } diff --git a/src/keyboard.rs b/src/keyboard.rs index 054a39580..bcb0650ac 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -92,7 +92,8 @@ pub mod client { if is_long_press(&event) { return; } - if let Some(key_event) = event_to_key_event(&event, lock_modes) { + + for key_event in event_to_key_events(&event, lock_modes) { send_key_event(&key_event); } } @@ -341,7 +342,7 @@ fn update_modifiers_state(event: &Event) { }; } -pub fn event_to_key_event(event: &Event, lock_modes: Option) -> Option { +pub fn event_to_key_events(event: &Event, lock_modes: Option) -> Vec { let mut key_event = KeyEvent::new(); update_modifiers_state(event); @@ -357,28 +358,38 @@ pub fn event_to_key_event(event: &Event, lock_modes: Option) -> Option map_keyboard_mode(event, key_event)?, - KeyboardMode::Translate => translate_keyboard_mode(event, key_event)?, + let mut key_events = match keyboard_mode { + KeyboardMode::Map => match map_keyboard_mode(event, key_event) { + Some(event) => [event].to_vec(), + None => Vec::new(), + }, + KeyboardMode::Translate => translate_keyboard_mode(event, key_event), _ => { #[cfg(not(any(target_os = "android", target_os = "ios")))] { - legacy_keyboard_mode(event, key_event)? + legacy_keyboard_mode(event, key_event) } #[cfg(any(target_os = "android", target_os = "ios"))] { - None? + Vec::new() } } }; - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if let Some(lock_modes) = lock_modes { - add_numlock_capslock_with_lock_modes(&mut key_event, lock_modes); - } else { - add_numlock_capslock_status(&mut key_event); + + if keyboard_mode != KeyboardMode::Translate { + for key_event in &mut key_events { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if let Some(lock_modes) = lock_modes { + add_numlock_capslock_with_lock_modes(key_event, lock_modes); + } else { + add_numlock_capslock_status(key_event); + } + } } - return Some(key_event); + println!("REMOVE ME ========================= key_events {:?}", &key_events); + + key_events } pub fn event_type_to_event(event_type: EventType) -> Event { @@ -386,6 +397,7 @@ pub fn event_type_to_event(event_type: EventType) -> Event { event_type, time: SystemTime::now(), name: None, + unicode: Vec::new(), code: 0, scan_code: 0, } @@ -423,13 +435,14 @@ pub fn get_peer_platform() -> String { } #[cfg(not(any(target_os = "android", target_os = "ios")))] -pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option { +pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec { + let mut events = Vec::new(); // legacy mode(0): Generate characters locally, look for keycode on other side. let (mut key, down_or_up) = match event.event_type { EventType::KeyPress(key) => (key, true), EventType::KeyRelease(key) => (key, false), _ => { - return None; + return events; } }; @@ -475,7 +488,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option Option { if is_win && ctrl && alt { client::ctrl_alt_del(); - return None; + return events; } Some(ControlKey::Delete) } @@ -545,7 +558,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option Some(ControlKey::Subtract), Key::KpPlus => Some(ControlKey::Add), Key::CapsLock | Key::NumLock | Key::ScrollLock => { - return None; + return events; } Key::Home => Some(ControlKey::Home), Key::End => Some(ControlKey::End), @@ -628,12 +641,12 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option Option Option { @@ -703,6 +717,51 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option Option { - None +#[cfg(target_os = "windows")] +fn is_modifier_code(scan_code: u32) -> bool { + match scan_code { + // Alt | AltGr | ControlLeft | ControlRight | ShiftLeft | ShiftRight | MetaLeft | MetaRight + 0x38 | 0xE038 | 0x1D | 0xE01D | 0x2A | 0x36 | 0xE05B | 0xE05C => true, + _ => false, + } +} + +#[cfg(target_os = "linux")] +fn is_modifier_code(key_code: u32) -> bool { + match scan_code { + 64 | 108 | 37 | 105 | 50 | 62 | 133 | 134 => true, + _ => false, + } +} + +#[cfg(target_os = "macos")] +fn is_modifier_code(key_code: u32) -> bool { + match scan_code { + 0x3A | 0x3D | 0x3B | 0x3E | 0x38 | 0x3C | 0x37 | 0x36 => true, + _ => false, + } +} + +pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec { + #[cfg(target_os = "windows")] + let is_modifier = is_modifier_code(event.scan_code); + #[cfg(target_os = "linux")] + let is_modifier = is_modifier_code(event.key_code); + #[cfg(target_os = "macos")] + let is_modifier = is_modifier_code(event.key_code); + + let mut events: Vec = Vec::new(); + if is_modifier { + if let Some(evt) = map_keyboard_mode(event, key_event) { + events.push(evt); + } + return events; + } + + for unicode in &event.unicode { + let mut evt = key_event.clone(); + evt.set_unicode(*unicode as _); + events.push(evt); + } + events } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 2715a2643..072ef53fb 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1067,6 +1067,21 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { release_keys(&mut en, &to_release); } +fn translate_keyboard_mode(evt: &KeyEvent) { + match evt.union { + Some(key_event::Union::Unicode(unicode)) => { + println!("REMOVE ME ========================= simulate_unicode {}", unicode); + allow_err!(rdev::simulate_unicode(unicode as _)); + }, + Some(key_event::Union::Chr(..)) => { + map_keyboard_mode(evt) + } + _ => { + log::debug!("Unreachable. Unexpected key event {:?}", &evt); + } + } +} + pub fn handle_key_(evt: &KeyEvent) { if EXITING.load(Ordering::SeqCst) { return; @@ -1080,7 +1095,7 @@ pub fn handle_key_(evt: &KeyEvent) { map_keyboard_mode(evt); } KeyboardMode::Translate => { - legacy_keyboard_mode(evt); + translate_keyboard_mode(evt); } _ => { legacy_keyboard_mode(evt); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 4fc5db743..95b8cdbd0 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -361,11 +361,31 @@ impl Session { } pub fn enter(&self) { + #[cfg(target_os = "windows")] + { + match &self.lc.read().unwrap().keyboard_mode as _ { + "legacy" => { + println!("REMOVE ME =========================== enter legacy "); + rdev::set_get_key_name(true); + } + "translate" => { + println!("REMOVE ME =========================== enter translate "); + rdev::set_get_key_name(true); + } + _ => {} + } + } + IS_IN.store(true, Ordering::SeqCst); keyboard::client::change_grab_status(GrabState::Run); } pub fn leave(&self) { + #[cfg(target_os = "windows")] + { + println!("REMOVE ME =========================== leave "); + rdev::set_get_key_name(false); + } IS_IN.store(false, Ordering::SeqCst); keyboard::client::change_grab_status(GrabState::Wait); } @@ -429,6 +449,7 @@ impl Session { let event = Event { time: std::time::SystemTime::now(), name: Option::Some(name.to_owned()), + unicode: Vec::new(), code: keycode as _, scan_code: scancode as _, event_type: event_type, From 6e54cd2e6b7a7e207b13e31f2668db4df98f13ee Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 3 Feb 2023 10:41:47 +0800 Subject: [PATCH 101/117] win, translate mode, check dead code Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_menubar.dart | 29 +++++---- src/common.rs | 4 +- src/keyboard.rs | 63 ++++++------------- src/ui_session_interface.rs | 5 +- 4 files changed, 41 insertions(+), 60 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 36b9504c0..4fd702ad8 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1382,25 +1382,23 @@ class _RemoteMenubarState extends State { text: translate('Ratio'), optionsGetter: () { List list = []; - List modes = ["legacy"]; + List modes = [ + KeyboardModeMenu(key: 'legacy', menu: 'Legacy mode'), + KeyboardModeMenu(key: 'map', menu: 'Map mode'), + KeyboardModeMenu(key: 'translate', menu: 'Translate mode'), + ]; - if (bind.sessionIsKeyboardModeSupported(id: widget.id, mode: "map")) { - modes.add("map"); - } - - for (String mode in modes) { - if (mode == "legacy") { + for (KeyboardModeMenu mode in modes) { + if (bind.sessionIsKeyboardModeSupported( + id: widget.id, mode: mode.key)) { list.add(MenuEntryRadioOption( - text: translate('Legacy mode'), value: 'legacy')); - } else if (mode == "map") { - list.add(MenuEntryRadioOption( - text: translate('Map mode'), value: 'map')); + text: translate(mode.menu), value: mode.key)); } } return list; }, curOptionGetter: () async { - return await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy"; + return await bind.sessionGetKeyboardMode(id: widget.id) ?? 'legacy'; }, optionSetter: (String oldValue, String newValue) async { await bind.sessionSetKeyboardMode(id: widget.id, value: newValue); @@ -1689,3 +1687,10 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { ); } } + +class KeyboardModeMenu { + final String key; + final String menu; + + KeyboardModeMenu({required this.key, required this.menu}); +} diff --git a/src/common.rs b/src/common.rs index c2d5a81f0..8f8ce8dec 100644 --- a/src/common.rs +++ b/src/common.rs @@ -671,8 +671,8 @@ pub fn is_keyboard_mode_supported(keyboard_mode: &KeyboardMode, version_number: match keyboard_mode { KeyboardMode::Legacy => true, KeyboardMode::Map => version_number >= hbb_common::get_version_number("1.2.0"), - KeyboardMode::Translate => false, - KeyboardMode::Auto => false, + KeyboardMode::Translate => version_number >= hbb_common::get_version_number("1.2.0"), + KeyboardMode::Auto => version_number >= hbb_common::get_version_number("1.2.0"), } } diff --git a/src/keyboard.rs b/src/keyboard.rs index bcb0650ac..7d5f36af2 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -387,7 +387,10 @@ pub fn event_to_key_events(event: &Event, lock_modes: Option) -> Vec Event { Event { event_type, time: SystemTime::now(), - name: None, - unicode: Vec::new(), + unicode: None, code: 0, scan_code: 0, } @@ -571,7 +573,8 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec { if s.len() <= 2 { // exclude chinese characters @@ -717,51 +720,25 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option bool { - match scan_code { - // Alt | AltGr | ControlLeft | ControlRight | ShiftLeft | ShiftRight | MetaLeft | MetaRight - 0x38 | 0xE038 | 0x1D | 0xE01D | 0x2A | 0x36 | 0xE05B | 0xE05C => true, - _ => false, - } -} - -#[cfg(target_os = "linux")] -fn is_modifier_code(key_code: u32) -> bool { - match scan_code { - 64 | 108 | 37 | 105 | 50 | 62 | 133 | 134 => true, - _ => false, - } -} - -#[cfg(target_os = "macos")] -fn is_modifier_code(key_code: u32) -> bool { - match scan_code { - 0x3A | 0x3D | 0x3B | 0x3E | 0x38 | 0x3C | 0x37 | 0x36 => true, - _ => false, - } -} - pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec { - #[cfg(target_os = "windows")] - let is_modifier = is_modifier_code(event.scan_code); - #[cfg(target_os = "linux")] - let is_modifier = is_modifier_code(event.key_code); - #[cfg(target_os = "macos")] - let is_modifier = is_modifier_code(event.key_code); - let mut events: Vec = Vec::new(); - if is_modifier { + match &event.unicode { + Some(unicode_info) => { + if !unicode_info.is_dead { + for code in &unicode_info.unicode { + let mut evt = key_event.clone(); + evt.set_unicode(*code as _); + events.push(evt); + } + } + } + None => {} + } + if events.is_empty() { if let Some(evt) = map_keyboard_mode(event, key_event) { events.push(evt); } return events; } - - for unicode in &event.unicode { - let mut evt = key_event.clone(); - evt.set_unicode(*unicode as _); - events.push(evt); - } events } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 95b8cdbd0..3801eda67 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -423,7 +423,7 @@ impl Session { pub fn handle_flutter_key_event( &self, - name: &str, + _name: &str, keycode: i32, scancode: i32, lock_modes: i32, @@ -448,8 +448,7 @@ impl Session { }; let event = Event { time: std::time::SystemTime::now(), - name: Option::Some(name.to_owned()), - unicode: Vec::new(), + unicode: None, code: keycode as _, scan_code: scancode as _, event_type: event_type, From 6eec0041bd7038a062a250b7f6dbd8bb3b4569d1 Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 6 Feb 2023 16:26:27 +0800 Subject: [PATCH 102/117] win, tranlsate mode, handle shift Signed-off-by: fufesou --- src/keyboard.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index 7d5f36af2..08ab23b1b 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -88,14 +88,17 @@ pub mod client { } } - pub fn process_event(event: &Event, lock_modes: Option) { + pub fn process_event(event: &Event, lock_modes: Option) -> KeyboardMode { + let keyboard_mode = get_keyboard_mode_enum(); + if is_long_press(&event) { - return; + return keyboard_mode; } - for key_event in event_to_key_events(&event, lock_modes) { + for key_event in event_to_key_events(&event, keyboard_mode, lock_modes) { send_key_event(&key_event); } + keyboard_mode } pub fn get_modifiers_state( @@ -205,7 +208,14 @@ pub fn start_grab_loop() { return Some(event); } if KEYBOARD_HOOKED.load(Ordering::SeqCst) { - client::process_event(&event, None); + let keyboard_mode = client::process_event(&event, None); + if keyboard_mode == KeyboardMode::Translate { + // shift + if event.scan_code == 0x2A { + return Some(event); + } + } + if is_press { return None; } else { @@ -342,7 +352,7 @@ fn update_modifiers_state(event: &Event) { }; } -pub fn event_to_key_events(event: &Event, lock_modes: Option) -> Vec { +pub fn event_to_key_events(event: &Event, keyboard_mode: KeyboardMode, lock_modes: Option) -> Vec { let mut key_event = KeyEvent::new(); update_modifiers_state(event); @@ -356,7 +366,6 @@ pub fn event_to_key_events(event: &Event, lock_modes: Option) -> Vec {} } - let keyboard_mode = get_keyboard_mode_enum(); key_event.mode = keyboard_mode.into(); let mut key_events = match keyboard_mode { KeyboardMode::Map => match map_keyboard_mode(event, key_event) { From ddc9792d15420a2a3e56cc6b37cc89a064c5013b Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 6 Feb 2023 18:13:17 +0800 Subject: [PATCH 103/117] win, translate mode, debug almost done Signed-off-by: fufesou --- Cargo.lock | 28 ++----------------- Cargo.toml | 2 +- .../lib/desktop/widgets/remote_menubar.dart | 6 ++++ src/keyboard.rs | 9 ++---- src/server/input_service.rs | 1 - src/ui_session_interface.rs | 11 ++------ 6 files changed, 14 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ac2720be..988363019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,7 @@ dependencies = [ "log", "objc", "pkg-config", - "rdev 0.5.0-2 (git+https://github.com/fufesou/rdev)", + "rdev", "serde 1.0.149", "serde_derive", "tfc", @@ -4404,29 +4404,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -dependencies = [ - "cocoa", - "core-foundation 0.9.3", - "core-foundation-sys 0.8.3", - "core-graphics 0.22.3", - "enum-map", - "epoll", - "inotify", - "lazy_static", - "libc", - "log", - "mio 0.8.5", - "strum 0.24.1", - "strum_macros 0.24.3", - "widestring 1.0.2", - "winapi 0.3.9", - "x11 2.20.1", -] - -[[package]] -name = "rdev" -version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#238c9778da40056e2efda1e4264355bc89fb6358" +source = "git+https://github.com/fufesou/rdev#77b45e9e43f713851874c7fbb8e7149ab4f2e6a1" dependencies = [ "cocoa", "core-foundation 0.9.3", @@ -4731,7 +4709,7 @@ dependencies = [ "objc", "objc_id", "parity-tokio-ipc", - "rdev 0.5.0-2", + "rdev", "repng", "reqwest", "rpassword 7.2.0", diff --git a/Cargo.toml b/Cargo.toml index 5d75b7a23..936b9e349 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ default-net = { git = "https://github.com/Kingtous/default-net" } wol-rs = "0.9.1" flutter_rust_bridge = { version = "1.61.1", optional = true } errno = "0.2.8" -rdev = { path = "../rdev" } +rdev = { git = "https://github.com/fufesou/rdev" } url = { version = "2.1", features = ["serde"] } reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 4fd702ad8..c3c8ce3fe 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1391,6 +1391,12 @@ class _RemoteMenubarState extends State { for (KeyboardModeMenu mode in modes) { if (bind.sessionIsKeyboardModeSupported( id: widget.id, mode: mode.key)) { + if (mode.key == 'translate') { + if (!Platform.isWindows || + widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) { + continue; + } + } list.add(MenuEntryRadioOption( text: translate(mode.menu), value: mode.key)); } diff --git a/src/keyboard.rs b/src/keyboard.rs index 08ab23b1b..fd9514427 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -210,8 +210,8 @@ pub fn start_grab_loop() { if KEYBOARD_HOOKED.load(Ordering::SeqCst) { let keyboard_mode = client::process_event(&event, None); if keyboard_mode == KeyboardMode::Translate { - // shift - if event.scan_code == 0x2A { + // SHIFT(0x2A) RSHIFT(0x36) + if event.scan_code == 0x2A || event.scan_code == 0x36 { return Some(event); } } @@ -396,11 +396,6 @@ pub fn event_to_key_events(event: &Event, keyboard_mode: KeyboardMode, lock_mode } } - println!( - "REMOVE ME ========================= key_events {:?}", - &key_events - ); - key_events } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 072ef53fb..1d7d4773d 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1070,7 +1070,6 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { fn translate_keyboard_mode(evt: &KeyEvent) { match evt.union { Some(key_event::Union::Unicode(unicode)) => { - println!("REMOVE ME ========================= simulate_unicode {}", unicode); allow_err!(rdev::simulate_unicode(unicode as _)); }, Some(key_event::Union::Chr(..)) => { diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 3801eda67..12412d7cd 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -364,14 +364,8 @@ impl Session { #[cfg(target_os = "windows")] { match &self.lc.read().unwrap().keyboard_mode as _ { - "legacy" => { - println!("REMOVE ME =========================== enter legacy "); - rdev::set_get_key_name(true); - } - "translate" => { - println!("REMOVE ME =========================== enter translate "); - rdev::set_get_key_name(true); - } + "legacy" => rdev::set_get_key_name(true), + "translate" => rdev::set_get_key_name(true), _ => {} } } @@ -383,7 +377,6 @@ impl Session { pub fn leave(&self) { #[cfg(target_os = "windows")] { - println!("REMOVE ME =========================== leave "); rdev::set_get_key_name(false); } IS_IN.store(false, Ordering::SeqCst); From 347add18744358b9d07247bee458f2a4ca17c0f8 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 09:48:04 +0800 Subject: [PATCH 104/117] win, translate mode, to debug Signed-off-by: fufesou --- Cargo.lock | 26 +++++++++- Cargo.toml | 2 +- src/keyboard.rs | 100 ++++++++++++++++++++++++++++++++---- src/server/input_service.rs | 13 ++++- 4 files changed, 128 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 988363019..f5ffa7f9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,7 @@ dependencies = [ "log", "objc", "pkg-config", - "rdev", + "rdev 0.5.0-2 (git+https://github.com/fufesou/rdev)", "serde 1.0.149", "serde_derive", "tfc", @@ -4401,6 +4401,28 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rdev" +version = "0.5.0-2" +dependencies = [ + "cocoa", + "core-foundation 0.9.3", + "core-foundation-sys 0.8.3", + "core-graphics 0.22.3", + "enum-map", + "epoll", + "inotify", + "lazy_static", + "libc", + "log", + "mio 0.8.5", + "strum 0.24.1", + "strum_macros 0.24.3", + "widestring 1.0.2", + "winapi 0.3.9", + "x11 2.20.1", +] + [[package]] name = "rdev" version = "0.5.0-2" @@ -4709,7 +4731,7 @@ dependencies = [ "objc", "objc_id", "parity-tokio-ipc", - "rdev", + "rdev 0.5.0-2", "repng", "reqwest", "rpassword 7.2.0", diff --git a/Cargo.toml b/Cargo.toml index 936b9e349..5d75b7a23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ default-net = { git = "https://github.com/Kingtous/default-net" } wol-rs = "0.9.1" flutter_rust_bridge = { version = "1.61.1", optional = true } errno = "0.2.8" -rdev = { git = "https://github.com/fufesou/rdev" } +rdev = { path = "../rdev" } url = { version = "2.1", features = ["serde"] } reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } diff --git a/src/keyboard.rs b/src/keyboard.rs index fd9514427..492314ab6 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -199,6 +199,9 @@ pub fn update_grab_get_key_name() { }; } +#[cfg(target_os = "windows")] +static mut IS_LAST_0X021D: bool = false; + pub fn start_grab_loop() { #[cfg(any(target_os = "windows", target_os = "macos"))] std::thread::spawn(move || { @@ -210,12 +213,22 @@ pub fn start_grab_loop() { if KEYBOARD_HOOKED.load(Ordering::SeqCst) { let keyboard_mode = client::process_event(&event, None); if keyboard_mode == KeyboardMode::Translate { - // SHIFT(0x2A) RSHIFT(0x36) - if event.scan_code == 0x2A || event.scan_code == 0x36 { - return Some(event); + #[cfg(target_os = "windows")] + match event.scan_code { + 0x2A => rdev::set_modifier(Key::ShiftLeft, is_press), + 0x36 => rdev::set_modifier(Key::ShiftRight, is_press), + 0x38 => rdev::set_modifier(Key::Alt, is_press), + 0xE038 => rdev::set_modifier(Key::AltGr, is_press), + 0xE05B => rdev::set_modifier(Key::MetaLeft, is_press), + 0xE05C => rdev::set_modifier(Key::MetaRight, is_press), + _ => {} + } + #[cfg(target_os = "windows")] + unsafe { + IS_LAST_0X021D = event.scan_code == 0x021D; } } - + if is_press { return None; } else { @@ -352,7 +365,11 @@ fn update_modifiers_state(event: &Event) { }; } -pub fn event_to_key_events(event: &Event, keyboard_mode: KeyboardMode, lock_modes: Option) -> Vec { +pub fn event_to_key_events( + event: &Event, + keyboard_mode: KeyboardMode, + lock_modes: Option, +) -> Vec { let mut key_event = KeyEvent::new(); update_modifiers_state(event); @@ -577,7 +594,10 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec { if s.len() <= 2 { @@ -724,8 +744,7 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option Vec { - let mut events: Vec = Vec::new(); +fn try_fill_unicode(event: &Event, key_event: &KeyEvent, events: &mut Vec) { match &event.unicode { Some(unicode_info) => { if !unicode_info.is_dead { @@ -738,8 +757,71 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec {} } +} + +#[cfg(target_os = "windows")] +fn is_hot_key_modifiers_down() -> bool { + if rdev::get_modifier(Key::ControlLeft) || rdev::get_modifier(Key::ControlRight) { + return true; + } + if rdev::get_modifier(Key::Alt) || rdev::get_modifier(Key::AltGr) { + return true; + } + if rdev::get_modifier(Key::MetaLeft) || rdev::get_modifier(Key::MetaRight) { + return true; + } + return false; +} + +pub fn translate_virtual_keycode(event: &Event, mut key_event: KeyEvent) -> Option { + match event.event_type { + EventType::KeyPress(..) => { + key_event.down = true; + } + EventType::KeyRelease(..) => { + key_event.down = false; + } + _ => return None, + }; + + let mut peer = get_peer_platform().to_lowercase(); + peer.retain(|c| !c.is_whitespace()); + + // #[cfg(target_os = "windows")] + // let keycode = match peer.as_str() { + // "windows" => event.code, + // "macos" => { + // if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" { + // rdev::win_scancode_to_macos_iso_code(event.scan_code)? + // } else { + // rdev::win_scancode_to_macos_code(event.scan_code)? + // } + // } + // _ => rdev::win_scancode_to_linux_code(event.scan_code)?, + // }; + + key_event.set_chr(event.code as _); + Some(key_event) +} + +pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec { + let mut events: Vec = Vec::new(); + #[cfg(target_os = "windows")] + unsafe { + if IS_LAST_0X021D { + if event.scan_code == 0xE038 { + return events; + } + } + } + + #[cfg(target_os = "windows")] + if !is_hot_key_modifiers_down() { + try_fill_unicode(event, &key_event, &mut events); + } + if events.is_empty() { - if let Some(evt) = map_keyboard_mode(event, key_event) { + if let Some(evt) = translate_virtual_keycode(event, key_event) { events.push(evt); } return events; diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 1d7d4773d..133b9a830 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1067,13 +1067,24 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { release_keys(&mut en, &to_release); } +#[cfg(target_os = "windows")] +fn translate_process_virtual_keycode(vk: u32, down: bool) { + let scancode = rdev::vk_to_scancode(vk); + // map mode(1): Send keycode according to the peer platform. + record_pressed_key(scancode as u64 + KEY_CHAR_START, down); + + crate::platform::windows::try_change_desktop(); + sim_rdev_rawkey(scancode, down); +} + fn translate_keyboard_mode(evt: &KeyEvent) { match evt.union { Some(key_event::Union::Unicode(unicode)) => { allow_err!(rdev::simulate_unicode(unicode as _)); }, Some(key_event::Union::Chr(..)) => { - map_keyboard_mode(evt) + #[cfg(target_os = "windows")] + translate_process_virtual_keycode(evt.chr(), evt.down) } _ => { log::debug!("Unreachable. Unexpected key event {:?}", &evt); From 1294103ba778016a118ba51cbf9a3d61f1db5212 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 13:31:49 +0800 Subject: [PATCH 105/117] win, translate mode, debug Signed-off-by: fufesou --- src/keyboard.rs | 62 +++++++++++++++++++------------- src/server/input_service.rs | 71 ++++++++++++++++++++++--------------- 2 files changed, 80 insertions(+), 53 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index 492314ab6..bdf1c5c1b 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -200,7 +200,7 @@ pub fn update_grab_get_key_name() { } #[cfg(target_os = "windows")] -static mut IS_LAST_0X021D: bool = false; +static mut IS_0X021D_DOWN: bool = false; pub fn start_grab_loop() { #[cfg(any(target_os = "windows", target_os = "macos"))] @@ -210,33 +210,43 @@ pub fn start_grab_loop() { if key == Key::CapsLock || key == Key::NumLock { return Some(event); } - if KEYBOARD_HOOKED.load(Ordering::SeqCst) { - let keyboard_mode = client::process_event(&event, None); - if keyboard_mode == KeyboardMode::Translate { - #[cfg(target_os = "windows")] - match event.scan_code { - 0x2A => rdev::set_modifier(Key::ShiftLeft, is_press), - 0x36 => rdev::set_modifier(Key::ShiftRight, is_press), - 0x38 => rdev::set_modifier(Key::Alt, is_press), - 0xE038 => rdev::set_modifier(Key::AltGr, is_press), - 0xE05B => rdev::set_modifier(Key::MetaLeft, is_press), - 0xE05C => rdev::set_modifier(Key::MetaRight, is_press), - _ => {} - } - #[cfg(target_os = "windows")] - unsafe { - IS_LAST_0X021D = event.scan_code == 0x021D; - } - } + let mut _keyboard_mode = KeyboardMode::Map; + let scan_code = event.scan_code; + let res = if KEYBOARD_HOOKED.load(Ordering::SeqCst) { + _keyboard_mode = client::process_event(&event, None); if is_press { - return None; + None } else { - return Some(event); + Some(event) } } else { - return Some(event); + Some(event) + }; + + #[cfg(target_os = "windows")] + match scan_code { + 0x1D | 0x021D => rdev::set_modifier(Key::ControlLeft, is_press), + 0xE01D => rdev::set_modifier(Key::ControlRight, is_press), + 0x2A => rdev::set_modifier(Key::ShiftLeft, is_press), + 0x36 => rdev::set_modifier(Key::ShiftRight, is_press), + 0x38 => rdev::set_modifier(Key::Alt, is_press), + // Right Alt + 0xE038 => rdev::set_modifier(Key::AltGr, is_press), + 0xE05B => rdev::set_modifier(Key::MetaLeft, is_press), + 0xE05C => rdev::set_modifier(Key::MetaRight, is_press), + _ => {} } + + #[cfg(target_os = "windows")] + unsafe { + // AltGr + if scan_code == 0x021D { + IS_0X021D_DOWN = is_press; + } + } + + return res; }; let func = move |event: Event| match event.event_type { EventType::KeyPress(key) => try_handle_keyboard(event, key, true), @@ -808,7 +818,11 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec = Vec::new(); #[cfg(target_os = "windows")] unsafe { - if IS_LAST_0X021D { + if event.scan_code == 0x021D { + return events; + } + + if IS_0X021D_DOWN { if event.scan_code == 0xE038 { return events; } @@ -816,7 +830,7 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec ResultType<()> Ok(()) } +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +enum KeysDown { + RdevKey(RawKey), + EnigoKey(u64) +} + lazy_static::lazy_static! { static ref ENIGO: Arc> = { Arc::new(Mutex::new(Enigo::new())) }; - static ref KEYS_DOWN: Arc>> = Default::default(); + static ref KEYS_DOWN: Arc>> = Default::default(); static ref LATEST_PEER_INPUT_CURSOR: Arc> = Default::default(); static ref LATEST_SYS_CURSOR_POS: Arc> = Arc::new(Mutex::new((Instant::now().sub(MOUSE_MOVE_PROTECTION_TIMEOUT), (0, 0)))); } @@ -375,12 +380,7 @@ fn record_key_is_control_key(record_key: u64) -> bool { #[inline] fn record_key_is_chr(record_key: u64) -> bool { - KEY_RDEV_START <= record_key && record_key < KEY_CHAR_START -} - -#[inline] -fn record_key_is_rdev_layout(record_key: u64) -> bool { - KEY_CHAR_START <= record_key + record_key < KEY_CHAR_START } #[inline] @@ -396,15 +396,18 @@ fn record_key_to_key(record_key: u64) -> Option { } #[inline] -fn release_record_key(record_key: u64) { +fn release_record_key(record_key: KeysDown) { let func = move || { - if record_key_is_rdev_layout(record_key) { - simulate_(&EventType::KeyRelease(RdevKey::Unknown( - (record_key - KEY_RDEV_START) as _, - ))); - } else if let Some(key) = record_key_to_key(record_key) { - ENIGO.lock().unwrap().key_up(key); - log::debug!("Fixed {:?} timeout", key); + match record_key { + KeysDown::RdevKey(raw_key) => { + simulate_(&EventType::KeyRelease(RdevKey::RawKey(raw_key))); + } + KeysDown::EnigoKey(key) => { + if let Some(key) = record_key_to_key(key) { + ENIGO.lock().unwrap().key_up(key); + log::debug!("Fixed {:?} timeout", key); + } + } } }; @@ -733,7 +736,7 @@ pub fn reset_input_ondisconn() { } } -fn sim_rdev_rawkey(code: u32, keydown: bool) { +fn sim_rdev_rawkey_position(code: u32, keydown: bool) { #[cfg(target_os = "windows")] let rawkey = RawKey::ScanCode(code); #[cfg(target_os = "linux")] @@ -744,6 +747,23 @@ fn sim_rdev_rawkey(code: u32, keydown: bool) { #[cfg(target_os = "macos")] let rawkey = RawKey::MacVirtualKeycode(code); + // map mode(1): Send keycode according to the peer platform. + record_pressed_key(KeysDown::RdevKey(rawkey), keydown); + + let event_type = if keydown { + EventType::KeyPress(RdevKey::RawKey(rawkey)) + } else { + EventType::KeyRelease(RdevKey::RawKey(rawkey)) + }; + simulate_(&event_type); +} + +fn sim_rdev_rawkey_virtual(code: u32, keydown: bool) { + #[cfg(target_os = "windows")] + let rawkey = RawKey::WinVirtualKeycode(code); + + record_pressed_key(KeysDown::RdevKey(rawkey), keydown); + let event_type = if keydown { EventType::KeyPress(RdevKey::RawKey(rawkey)) } else { @@ -874,9 +894,6 @@ fn sync_numlock_capslock_status(key_event: &KeyEvent) { } fn map_keyboard_mode(evt: &KeyEvent) { - // map mode(1): Send keycode according to the peer platform. - record_pressed_key(evt.chr() as u64 + KEY_CHAR_START, evt.down); - #[cfg(windows)] crate::platform::windows::try_change_desktop(); @@ -894,7 +911,7 @@ fn map_keyboard_mode(evt: &KeyEvent) { return; } - sim_rdev_rawkey(evt.chr(), evt.down); + sim_rdev_rawkey_position(evt.chr(), evt.down); } #[cfg(target_os = "macos")] @@ -1011,7 +1028,7 @@ fn release_keys(en: &mut Enigo, to_release: &Vec) { } } -fn record_pressed_key(record_key: u64, down: bool) { +fn record_pressed_key(record_key: KeysDown, down: bool) { let mut key_down = KEYS_DOWN.lock().unwrap(); if down { key_down.insert(record_key, Instant::now()); @@ -1050,12 +1067,12 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { return; } let record_key = ck.value() as u64; - record_pressed_key(record_key, down); + record_pressed_key(KeysDown::EnigoKey(record_key), down); process_control_key(&mut en, &ck, down) } Some(key_event::Union::Chr(chr)) => { let record_key = chr as u64 + KEY_CHAR_START; - record_pressed_key(record_key, down); + record_pressed_key(KeysDown::EnigoKey(record_key), down); process_chr(&mut en, chr, down) } Some(key_event::Union::Unicode(chr)) => process_unicode(&mut en, chr), @@ -1069,12 +1086,8 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { #[cfg(target_os = "windows")] fn translate_process_virtual_keycode(vk: u32, down: bool) { - let scancode = rdev::vk_to_scancode(vk); - // map mode(1): Send keycode according to the peer platform. - record_pressed_key(scancode as u64 + KEY_CHAR_START, down); - crate::platform::windows::try_change_desktop(); - sim_rdev_rawkey(scancode, down); + sim_rdev_rawkey_virtual(vk, down); } fn translate_keyboard_mode(evt: &KeyEvent) { From 5c7f2678fa870427975bdc98c29c7126bb35ba58 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 14:23:05 +0800 Subject: [PATCH 106/117] update rdev Signed-off-by: fufesou --- Cargo.lock | 26 ++------------------------ Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5ffa7f9d..988363019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,7 @@ dependencies = [ "log", "objc", "pkg-config", - "rdev 0.5.0-2 (git+https://github.com/fufesou/rdev)", + "rdev", "serde 1.0.149", "serde_derive", "tfc", @@ -4401,28 +4401,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rdev" -version = "0.5.0-2" -dependencies = [ - "cocoa", - "core-foundation 0.9.3", - "core-foundation-sys 0.8.3", - "core-graphics 0.22.3", - "enum-map", - "epoll", - "inotify", - "lazy_static", - "libc", - "log", - "mio 0.8.5", - "strum 0.24.1", - "strum_macros 0.24.3", - "widestring 1.0.2", - "winapi 0.3.9", - "x11 2.20.1", -] - [[package]] name = "rdev" version = "0.5.0-2" @@ -4731,7 +4709,7 @@ dependencies = [ "objc", "objc_id", "parity-tokio-ipc", - "rdev 0.5.0-2", + "rdev", "repng", "reqwest", "rpassword 7.2.0", diff --git a/Cargo.toml b/Cargo.toml index 5d75b7a23..936b9e349 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ default-net = { git = "https://github.com/Kingtous/default-net" } wol-rs = "0.9.1" flutter_rust_bridge = { version = "1.61.1", optional = true } errno = "0.2.8" -rdev = { path = "../rdev" } +rdev = { git = "https://github.com/fufesou/rdev" } url = { version = "2.1", features = ["serde"] } reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } From 01f762ffdb57d7dee4a17110d293252b1621c49a Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 14:14:13 +0800 Subject: [PATCH 107/117] build linux Signed-off-by: fufesou --- src/keyboard.rs | 5 ++++- src/server/input_service.rs | 33 ++++++++++++++++----------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index bdf1c5c1b..91480ba30 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -830,10 +830,13 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec ResultType<()> #[derive(Copy, Clone, PartialEq, Eq, Hash)] enum KeysDown { RdevKey(RawKey), - EnigoKey(u64) + EnigoKey(u64), } lazy_static::lazy_static! { @@ -397,16 +397,14 @@ fn record_key_to_key(record_key: u64) -> Option { #[inline] fn release_record_key(record_key: KeysDown) { - let func = move || { - match record_key { - KeysDown::RdevKey(raw_key) => { - simulate_(&EventType::KeyRelease(RdevKey::RawKey(raw_key))); - } - KeysDown::EnigoKey(key) => { - if let Some(key) = record_key_to_key(key) { - ENIGO.lock().unwrap().key_up(key); - log::debug!("Fixed {:?} timeout", key); - } + let func = move || match record_key { + KeysDown::RdevKey(raw_key) => { + simulate_(&EventType::KeyRelease(RdevKey::RawKey(raw_key))); + } + KeysDown::EnigoKey(key) => { + if let Some(key) = record_key_to_key(key) { + ENIGO.lock().unwrap().key_up(key); + log::debug!("Fixed {:?} timeout", key); } } }; @@ -758,12 +756,10 @@ fn sim_rdev_rawkey_position(code: u32, keydown: bool) { simulate_(&event_type); } +#[cfg(target_os = "windows")] fn sim_rdev_rawkey_virtual(code: u32, keydown: bool) { - #[cfg(target_os = "windows")] let rawkey = RawKey::WinVirtualKeycode(code); - record_pressed_key(KeysDown::RdevKey(rawkey), keydown); - let event_type = if keydown { EventType::KeyPress(RdevKey::RawKey(rawkey)) } else { @@ -941,10 +937,11 @@ fn release_unpressed_modifiers(en: &mut Enigo, key_event: &KeyEvent) { #[cfg(target_os = "linux")] fn is_altgr_pressed() -> bool { + let altgr_rawkey = RawKey::LinuxXorgKeycode(ControlKey::RAlt.value() as _); KEYS_DOWN .lock() .unwrap() - .get(&(ControlKey::RAlt.value() as _)) + .get(&KeysDown::RdevKey(altgr_rawkey)) .is_some() } @@ -1093,9 +1090,11 @@ fn translate_process_virtual_keycode(vk: u32, down: bool) { fn translate_keyboard_mode(evt: &KeyEvent) { match evt.union { Some(key_event::Union::Unicode(unicode)) => { + #[cfg(target_os = "windows")] allow_err!(rdev::simulate_unicode(unicode as _)); - }, - Some(key_event::Union::Chr(..)) => { + } + Some(key_event::Union::Chr(..)) => + { #[cfg(target_os = "windows")] translate_process_virtual_keycode(evt.chr(), evt.down) } From 586f0a272663222c6d013aca3129c4be0dfd0fae Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 14:17:37 +0800 Subject: [PATCH 108/117] compile macos Signed-off-by: fufesou --- src/server/input_service.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 7b6130ad1..edf0ef497 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1089,9 +1089,9 @@ fn translate_process_virtual_keycode(vk: u32, down: bool) { fn translate_keyboard_mode(evt: &KeyEvent) { match evt.union { - Some(key_event::Union::Unicode(unicode)) => { + Some(key_event::Union::Unicode(_unicode)) => { #[cfg(target_os = "windows")] - allow_err!(rdev::simulate_unicode(unicode as _)); + allow_err!(rdev::simulate_unicode(_unicode as _)); } Some(key_event::Union::Chr(..)) => { From d263d1892bf6fc1258c5b922b8cbd3b0922deebe Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 14:34:52 +0800 Subject: [PATCH 109/117] update rdev Signed-off-by: fufesou --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 988363019..93b40ca3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4404,7 +4404,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#77b45e9e43f713851874c7fbb8e7149ab4f2e6a1" +source = "git+https://github.com/fufesou/rdev#4d8231f05e14c5a04cd7d2c1288e87ad52d39e4c" dependencies = [ "cocoa", "core-foundation 0.9.3", From 948f9f28dbbd2846e8026f595021d7fb2a7c0b73 Mon Sep 17 00:00:00 2001 From: NicKoehler <53040044+NicKoehler@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:15:08 +0100 Subject: [PATCH 110/117] Update it.rs --- src/lang/it.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index 9730bbc2d..a4ea58304 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -446,8 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "Auto"), ("Other Default Options", "Altre Opzioni Predefinite"), - ("Voice call", ""), - ("Text chat", ""), - ("Stop voice call", ""), + ("Voice call", "Chiamata vocale"), + ("Text chat", "Chat testuale"), + ("Stop voice call", "Interrompi la chiamata vocale"), ].iter().cloned().collect(); } From 7c13be587638c61ffee6fe09a82c2e114ffd2531 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 8 Feb 2023 17:26:44 +0800 Subject: [PATCH 111/117] update issue template and clippy for hbb_common --- .github/ISSUE_TEMPLATE/bug_report.yaml | 15 ++++-- libs/hbb_common/build.rs | 4 +- libs/hbb_common/src/bytes_codec.rs | 40 ++++++++-------- libs/hbb_common/src/config.rs | 6 +-- libs/hbb_common/src/lib.rs | 60 +++++++++++------------- libs/hbb_common/src/password_security.rs | 48 +++++++++---------- libs/hbb_common/src/platform/linux.rs | 2 +- libs/hbb_common/src/protos/mod.rs | 2 +- libs/hbb_common/src/socket_client.rs | 22 ++++----- libs/hbb_common/src/tcp.rs | 2 +- 10 files changed, 103 insertions(+), 98 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 16509a3be..c2d92097c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -30,13 +30,22 @@ body: description: A clear and concise description of what you expected to happen validations: required: true + - type: input + id: os + attributes: + label: Operating system(s) on local side and remote side + description: What operating system(s) do you see this bug on? local side -> remote side. + placeholder: | + Windows 10 -> osx + validations: + required: true - type: input id: version attributes: - label: Operating System(s) and RustDesk Version(s) on local side and remote side - description: What Operatiing System(s) and version(s) of RustDesk do you see this bug on? local side / remote side. + label: RustDesk Version(s) on local side and remote side + description: What RustDesk version(s) do you see this bug on? local side -> remote side. placeholder: | - Windows 10, 1.1.9 / osx 13.1, 1.1.8 + 1.1.9 -> 1.1.8 validations: required: true - type: textarea diff --git a/libs/hbb_common/build.rs b/libs/hbb_common/build.rs index fe0d31076..5ebc3a287 100644 --- a/libs/hbb_common/build.rs +++ b/libs/hbb_common/build.rs @@ -2,11 +2,11 @@ fn main() { let out_dir = format!("{}/protos", std::env::var("OUT_DIR").unwrap()); std::fs::create_dir_all(&out_dir).unwrap(); - + protobuf_codegen::Codegen::new() .pure() .out_dir(out_dir) - .inputs(&["protos/rendezvous.proto", "protos/message.proto"]) + .inputs(["protos/rendezvous.proto", "protos/message.proto"]) .include("protos") .customize(protobuf_codegen::Customize::default().tokio_bytes(true)) .run() diff --git a/libs/hbb_common/src/bytes_codec.rs b/libs/hbb_common/src/bytes_codec.rs index 699aa9bff..bfc798715 100644 --- a/libs/hbb_common/src/bytes_codec.rs +++ b/libs/hbb_common/src/bytes_codec.rs @@ -143,32 +143,32 @@ mod tests { let mut buf = BytesMut::new(); let mut bytes: Vec = Vec::new(); bytes.resize(0x3F, 1); - assert!(!codec.encode(bytes.into(), &mut buf).is_err()); + assert!(codec.encode(bytes.into(), &mut buf).is_ok()); let buf_saved = buf.clone(); assert_eq!(buf.len(), 0x3F + 1); if let Ok(Some(res)) = codec.decode(&mut buf) { assert_eq!(res.len(), 0x3F); assert_eq!(res[0], 1); } else { - assert!(false); + panic!(); } let mut codec2 = BytesCodec::new(); let mut buf2 = BytesMut::new(); if let Ok(None) = codec2.decode(&mut buf2) { } else { - assert!(false); + panic!(); } buf2.extend(&buf_saved[0..1]); if let Ok(None) = codec2.decode(&mut buf2) { } else { - assert!(false); + panic!(); } buf2.extend(&buf_saved[1..]); if let Ok(Some(res)) = codec2.decode(&mut buf2) { assert_eq!(res.len(), 0x3F); assert_eq!(res[0], 1); } else { - assert!(false); + panic!(); } } @@ -177,21 +177,21 @@ mod tests { let mut codec = BytesCodec::new(); let mut buf = BytesMut::new(); let mut bytes: Vec = Vec::new(); - assert!(!codec.encode("".into(), &mut buf).is_err()); + assert!(codec.encode("".into(), &mut buf).is_ok()); assert_eq!(buf.len(), 1); bytes.resize(0x3F + 1, 2); - assert!(!codec.encode(bytes.into(), &mut buf).is_err()); + assert!(codec.encode(bytes.into(), &mut buf).is_ok()); assert_eq!(buf.len(), 0x3F + 2 + 2); if let Ok(Some(res)) = codec.decode(&mut buf) { assert_eq!(res.len(), 0); } else { - assert!(false); + panic!(); } if let Ok(Some(res)) = codec.decode(&mut buf) { assert_eq!(res.len(), 0x3F + 1); assert_eq!(res[0], 2); } else { - assert!(false); + panic!(); } } @@ -201,13 +201,13 @@ mod tests { let mut buf = BytesMut::new(); let mut bytes: Vec = Vec::new(); bytes.resize(0x3F - 1, 3); - assert!(!codec.encode(bytes.into(), &mut buf).is_err()); + assert!(codec.encode(bytes.into(), &mut buf).is_ok()); assert_eq!(buf.len(), 0x3F + 1 - 1); if let Ok(Some(res)) = codec.decode(&mut buf) { assert_eq!(res.len(), 0x3F - 1); assert_eq!(res[0], 3); } else { - assert!(false); + panic!(); } } #[test] @@ -216,13 +216,13 @@ mod tests { let mut buf = BytesMut::new(); let mut bytes: Vec = Vec::new(); bytes.resize(0x3FFF, 4); - assert!(!codec.encode(bytes.into(), &mut buf).is_err()); + assert!(codec.encode(bytes.into(), &mut buf).is_ok()); assert_eq!(buf.len(), 0x3FFF + 2); if let Ok(Some(res)) = codec.decode(&mut buf) { assert_eq!(res.len(), 0x3FFF); assert_eq!(res[0], 4); } else { - assert!(false); + panic!(); } } @@ -232,13 +232,13 @@ mod tests { let mut buf = BytesMut::new(); let mut bytes: Vec = Vec::new(); bytes.resize(0x3FFFFF, 5); - assert!(!codec.encode(bytes.into(), &mut buf).is_err()); + assert!(codec.encode(bytes.into(), &mut buf).is_ok()); assert_eq!(buf.len(), 0x3FFFFF + 3); if let Ok(Some(res)) = codec.decode(&mut buf) { assert_eq!(res.len(), 0x3FFFFF); assert_eq!(res[0], 5); } else { - assert!(false); + panic!(); } } @@ -248,33 +248,33 @@ mod tests { let mut buf = BytesMut::new(); let mut bytes: Vec = Vec::new(); bytes.resize(0x3FFFFF + 1, 6); - assert!(!codec.encode(bytes.into(), &mut buf).is_err()); + assert!(codec.encode(bytes.into(), &mut buf).is_ok()); let buf_saved = buf.clone(); assert_eq!(buf.len(), 0x3FFFFF + 4 + 1); if let Ok(Some(res)) = codec.decode(&mut buf) { assert_eq!(res.len(), 0x3FFFFF + 1); assert_eq!(res[0], 6); } else { - assert!(false); + panic!(); } let mut codec2 = BytesCodec::new(); let mut buf2 = BytesMut::new(); buf2.extend(&buf_saved[0..1]); if let Ok(None) = codec2.decode(&mut buf2) { } else { - assert!(false); + panic!(); } buf2.extend(&buf_saved[1..6]); if let Ok(None) = codec2.decode(&mut buf2) { } else { - assert!(false); + panic!(); } buf2.extend(&buf_saved[6..]); if let Ok(Some(res)) = codec2.decode(&mut buf2) { assert_eq!(res.len(), 0x3FFFFF + 1); assert_eq!(res[0], 6); } else { - assert!(false); + panic!(); } } } diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 71dd9a5c6..1e4d80c9f 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -288,7 +288,7 @@ fn patch(path: PathBuf) -> PathBuf { .trim() .to_owned(); if user != "root" { - return format!("/home/{}", user).into(); + return format!("/home/{user}").into(); } } } @@ -525,7 +525,7 @@ impl Config { let mut path: PathBuf = format!("/tmp/{}", *APP_NAME.read().unwrap()).into(); fs::create_dir(&path).ok(); fs::set_permissions(&path, fs::Permissions::from_mode(0o0777)).ok(); - path.push(format!("ipc{}", postfix)); + path.push(format!("ipc{postfix}")); path.to_str().unwrap_or("").to_owned() } } @@ -562,7 +562,7 @@ impl Config { .unwrap_or_default(); } if !rendezvous_server.contains(':') { - rendezvous_server = format!("{}:{}", rendezvous_server, RENDEZVOUS_PORT); + rendezvous_server = format!("{rendezvous_server}:{RENDEZVOUS_PORT}"); } rendezvous_server } diff --git a/libs/hbb_common/src/lib.rs b/libs/hbb_common/src/lib.rs index c9f9e90d7..1c49adfb7 100644 --- a/libs/hbb_common/src/lib.rs +++ b/libs/hbb_common/src/lib.rs @@ -211,11 +211,7 @@ pub fn gen_version() { // generate build date let build_date = format!("{}", chrono::Local::now().format("%Y-%m-%d %H:%M")); file.write_all( - format!( - "#[allow(dead_code)]\npub const BUILD_DATE: &str = \"{}\";", - build_date - ) - .as_bytes(), + format!("#[allow(dead_code)]\npub const BUILD_DATE: &str = \"{build_date}\";\n").as_bytes(), ) .ok(); file.sync_all().ok(); @@ -342,39 +338,39 @@ mod test { #[test] fn test_ipv6() { - assert_eq!(is_ipv6_str("1:2:3"), true); - assert_eq!(is_ipv6_str("[ab:2:3]:12"), true); - assert_eq!(is_ipv6_str("[ABEF:2a:3]:12"), true); - assert_eq!(is_ipv6_str("[ABEG:2a:3]:12"), false); - assert_eq!(is_ipv6_str("1[ab:2:3]:12"), false); - assert_eq!(is_ipv6_str("1.1.1.1"), false); - assert_eq!(is_ip_str("1.1.1.1"), true); - assert_eq!(is_ipv6_str("1:2:"), false); - assert_eq!(is_ipv6_str("1:2::0"), true); - assert_eq!(is_ipv6_str("[1:2::0]:1"), true); - assert_eq!(is_ipv6_str("[1:2::0]:"), false); - assert_eq!(is_ipv6_str("1:2::0]:1"), false); + assert!(is_ipv6_str("1:2:3")); + assert!(is_ipv6_str("[ab:2:3]:12")); + assert!(is_ipv6_str("[ABEF:2a:3]:12")); + assert!(!is_ipv6_str("[ABEG:2a:3]:12")); + assert!(!is_ipv6_str("1[ab:2:3]:12")); + assert!(!is_ipv6_str("1.1.1.1")); + assert!(is_ip_str("1.1.1.1")); + assert!(!is_ipv6_str("1:2:")); + assert!(is_ipv6_str("1:2::0")); + assert!(is_ipv6_str("[1:2::0]:1")); + assert!(!is_ipv6_str("[1:2::0]:")); + assert!(!is_ipv6_str("1:2::0]:1")); } #[test] fn test_hostname_port() { - assert_eq!(is_domain_port_str("a:12"), false); - assert_eq!(is_domain_port_str("a.b.c:12"), false); - assert_eq!(is_domain_port_str("test.com:12"), true); - assert_eq!(is_domain_port_str("test-UPPER.com:12"), true); - assert_eq!(is_domain_port_str("some-other.domain.com:12"), true); - assert_eq!(is_domain_port_str("under_score:12"), false); - assert_eq!(is_domain_port_str("a@bc:12"), false); - assert_eq!(is_domain_port_str("1.1.1.1:12"), false); - assert_eq!(is_domain_port_str("1.2.3:12"), false); - assert_eq!(is_domain_port_str("1.2.3.45:12"), false); - assert_eq!(is_domain_port_str("a.b.c:123456"), false); - assert_eq!(is_domain_port_str("---:12"), false); - assert_eq!(is_domain_port_str(".:12"), false); + assert!(!is_domain_port_str("a:12")); + assert!(!is_domain_port_str("a.b.c:12")); + assert!(is_domain_port_str("test.com:12")); + assert!(is_domain_port_str("test-UPPER.com:12")); + assert!(is_domain_port_str("some-other.domain.com:12")); + assert!(!is_domain_port_str("under_score:12")); + assert!(!is_domain_port_str("a@bc:12")); + assert!(!is_domain_port_str("1.1.1.1:12")); + assert!(!is_domain_port_str("1.2.3:12")); + assert!(!is_domain_port_str("1.2.3.45:12")); + assert!(!is_domain_port_str("a.b.c:123456")); + assert!(!is_domain_port_str("---:12")); + assert!(!is_domain_port_str(".:12")); // todo: should we also check for these edge cases? // out-of-range port - assert_eq!(is_domain_port_str("test.com:0"), true); - assert_eq!(is_domain_port_str("test.com:98989"), true); + assert!(is_domain_port_str("test.com:0")); + assert!(is_domain_port_str("test.com:98989")); } #[test] diff --git a/libs/hbb_common/src/password_security.rs b/libs/hbb_common/src/password_security.rs index 0b66107fc..ddfe28baa 100644 --- a/libs/hbb_common/src/password_security.rs +++ b/libs/hbb_common/src/password_security.rs @@ -192,51 +192,51 @@ mod test { let data = "Hello World"; let encrypted = encrypt_str_or_original(data, version); let (decrypted, succ, store) = decrypt_str_or_original(&encrypted, version); - println!("data: {}", data); - println!("encrypted: {}", encrypted); - println!("decrypted: {}", decrypted); + println!("data: {data}"); + println!("encrypted: {encrypted}"); + println!("decrypted: {decrypted}"); assert_eq!(data, decrypted); assert_eq!(version, &encrypted[..2]); - assert_eq!(succ, true); - assert_eq!(store, false); + assert!(succ); + assert!(!store); let (_, _, store) = decrypt_str_or_original(&encrypted, "99"); - assert_eq!(store, true); - assert_eq!(decrypt_str_or_original(&decrypted, version).1, false); + assert!(store); + assert!(!decrypt_str_or_original(&decrypted, version).1); assert_eq!(encrypt_str_or_original(&encrypted, version), encrypted); println!("test vec"); let data: Vec = vec![1, 2, 3, 4, 5, 6]; let encrypted = encrypt_vec_or_original(&data, version); let (decrypted, succ, store) = decrypt_vec_or_original(&encrypted, version); - println!("data: {:?}", data); - println!("encrypted: {:?}", encrypted); - println!("decrypted: {:?}", decrypted); + println!("data: {data:?}"); + println!("encrypted: {encrypted:?}"); + println!("decrypted: {decrypted:?}"); assert_eq!(data, decrypted); assert_eq!(version.as_bytes(), &encrypted[..2]); - assert_eq!(store, false); - assert_eq!(succ, true); + assert!(!store); + assert!(succ); let (_, _, store) = decrypt_vec_or_original(&encrypted, "99"); - assert_eq!(store, true); - assert_eq!(decrypt_vec_or_original(&decrypted, version).1, false); + assert!(store); + assert!(!decrypt_vec_or_original(&decrypted, version).1); assert_eq!(encrypt_vec_or_original(&encrypted, version), encrypted); println!("test original"); let data = version.to_string() + "Hello World"; let (decrypted, succ, store) = decrypt_str_or_original(&data, version); assert_eq!(data, decrypted); - assert_eq!(store, true); - assert_eq!(succ, false); + assert!(store); + assert!(!succ); let verbytes = version.as_bytes(); - let data: Vec = vec![verbytes[0] as u8, verbytes[1] as u8, 1, 2, 3, 4, 5, 6]; + let data: Vec = vec![verbytes[0], verbytes[1], 1, 2, 3, 4, 5, 6]; let (decrypted, succ, store) = decrypt_vec_or_original(&data, version); assert_eq!(data, decrypted); - assert_eq!(store, true); - assert_eq!(succ, false); + assert!(store); + assert!(!succ); let (_, succ, store) = decrypt_str_or_original("", version); - assert_eq!(store, false); - assert_eq!(succ, false); - let (_, succ, store) = decrypt_vec_or_original(&vec![], version); - assert_eq!(store, false); - assert_eq!(succ, false); + assert!(!store); + assert!(!succ); + let (_, succ, store) = decrypt_vec_or_original(&[], version); + assert!(!store); + assert!(!succ); } } diff --git a/libs/hbb_common/src/platform/linux.rs b/libs/hbb_common/src/platform/linux.rs index 716025dc7..7c107d11c 100644 --- a/libs/hbb_common/src/platform/linux.rs +++ b/libs/hbb_common/src/platform/linux.rs @@ -60,7 +60,7 @@ fn get_display_server_of_session(session: &str) -> String { .replace("TTY=", "") .trim_end() .into(); - if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty)) + if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{tty}.\\\\+Xorg\"")) // And check if Xorg is running on that tty { if xorg_results.trim_end() != "" { diff --git a/libs/hbb_common/src/protos/mod.rs b/libs/hbb_common/src/protos/mod.rs index c001c58fb..57d9b68fe 100644 --- a/libs/hbb_common/src/protos/mod.rs +++ b/libs/hbb_common/src/protos/mod.rs @@ -1 +1 @@ -include!(concat!(env!("OUT_DIR"), "/protos/mod.rs")); \ No newline at end of file +include!(concat!(env!("OUT_DIR"), "/protos/mod.rs")); diff --git a/libs/hbb_common/src/socket_client.rs b/libs/hbb_common/src/socket_client.rs index a034b4e12..2d9b5a984 100644 --- a/libs/hbb_common/src/socket_client.rs +++ b/libs/hbb_common/src/socket_client.rs @@ -13,22 +13,22 @@ use tokio_socks::{IntoTargetAddr, TargetAddr}; pub fn check_port(host: T, port: i32) -> String { let host = host.to_string(); if crate::is_ipv6_str(&host) { - if host.starts_with("[") { + if host.starts_with('[') { return host; } - return format!("[{}]:{}", host, port); + return format!("[{host}]:{port}"); } - if !host.contains(":") { - return format!("{}:{}", host, port); + if !host.contains(':') { + return format!("{host}:{port}"); } - return host; + host } #[inline] pub fn increase_port(host: T, offset: i32) -> String { let host = host.to_string(); if crate::is_ipv6_str(&host) { - if host.starts_with("[") { + if host.starts_with('[') { let tmp: Vec<&str> = host.split("]:").collect(); if tmp.len() == 2 { let port: i32 = tmp[1].parse().unwrap_or(0); @@ -37,8 +37,8 @@ pub fn increase_port(host: T, offset: i32) -> String { } } } - } else if host.contains(":") { - let tmp: Vec<&str> = host.split(":").collect(); + } else if host.contains(':') { + let tmp: Vec<&str> = host.split(':').collect(); if tmp.len() == 2 { let port: i32 = tmp[1].parse().unwrap_or(0); if port > 0 { @@ -46,7 +46,7 @@ pub fn increase_port(host: T, offset: i32) -> String { } } } - return host; + host } pub fn test_if_valid_server(host: &str) -> String { @@ -148,7 +148,7 @@ pub async fn query_nip_io(addr: &SocketAddr) -> ResultType { pub fn ipv4_to_ipv6(addr: String, ipv4: bool) -> String { if !ipv4 && crate::is_ipv4_str(&addr) { if let Some(ip) = addr.split(':').next() { - return addr.replace(ip, &format!("{}.nip.io", ip)); + return addr.replace(ip, &format!("{ip}.nip.io")); } } addr @@ -163,7 +163,7 @@ async fn test_target(target: &str) -> ResultType { tokio::net::lookup_host(target) .await? .next() - .context(format!("Failed to look up host for {}", target)) + .context(format!("Failed to look up host for {target}")) } #[inline] diff --git a/libs/hbb_common/src/tcp.rs b/libs/hbb_common/src/tcp.rs index a7ac4eb3a..f574e8309 100644 --- a/libs/hbb_common/src/tcp.rs +++ b/libs/hbb_common/src/tcp.rs @@ -100,7 +100,7 @@ impl FramedStream { } } } - bail!(format!("Failed to connect to {}", remote_addr)); + bail!(format!("Failed to connect to {remote_addr}")); } pub async fn connect<'a, 't, P, T>( From 4134b77680126f408e5ce88f7eb4c3ad5711b749 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 8 Feb 2023 19:17:59 +0800 Subject: [PATCH 112/117] improve ffi enum data size, fix compile warning on mac --- src/client/io_loop.rs | 2 +- src/common.rs | 7 +---- src/core_main.rs | 4 +-- src/ipc.rs | 28 +++++++++++++++---- src/keyboard.rs | 6 ++-- src/server.rs | 62 +++++++++++++++++++++--------------------- src/ui/macos.rs | 9 ++---- src/ui_cm_interface.rs | 4 ++- 8 files changed, 65 insertions(+), 57 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index f5792bce3..5186aff4d 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -25,7 +25,7 @@ use hbb_common::{allow_err, get_time, message_proto::*, sleep}; use hbb_common::{fs, log, Stream}; use crate::client::{ - new_voice_call_request, Client, CodecFormat, LoginConfigHandler, MediaData, MediaSender, + new_voice_call_request, Client, CodecFormat, MediaData, MediaSender, QualityStatus, MILLI1, SEC30, SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED, SERVER_KEYBOARD_ENABLED, }; diff --git a/src/common.rs b/src/common.rs index 2142d973d..79a4664db 100644 --- a/src/common.rs +++ b/src/common.rs @@ -30,7 +30,7 @@ use hbb_common::{ // #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))] use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all}; -use crate::ui_interface::{set_option, get_option}; +use crate::ui_interface::{get_option, set_option}; pub type NotifyMessageBox = fn(String, String, String, String) -> dyn Future; @@ -762,8 +762,3 @@ pub fn make_fd_to_json(id: i32, path: String, entries: &Vec) -> Strin fd_json.insert("entries".into(), json!(entries_out)); serde_json::to_string(&fd_json).unwrap_or("".into()) } - -#[cfg(test)] -mod test_common { - -} diff --git a/src/core_main.rs b/src/core_main.rs index 03d057eff..0af7026e9 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -1,6 +1,4 @@ -use std::future::Future; - -use hbb_common::{log, ResultType}; +use hbb_common::log; /// shared by flutter and sciter main function /// diff --git a/src/ipc.rs b/src/ipc.rs index 0ede560fc..699b0bcd7 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -16,10 +16,10 @@ use hbb_common::{ config::{self, Config, Config2}, futures::StreamExt as _, futures_util::sink::SinkExt, - log, password_security as password, ResultType, timeout, - tokio, + log, password_security as password, timeout, tokio, tokio::io::{AsyncRead, AsyncWrite}, tokio_util::codec::Framed, + ResultType, }; use crate::rendezvous_mediator::RendezvousMediator; @@ -190,7 +190,7 @@ pub enum Data { Socks(Option), FS(FS), Test, - SyncConfig(Option<(Config, Config2)>), + SyncConfig(Option>), #[cfg(not(any(target_os = "android", target_os = "ios")))] ClipboardFile(ClipboardFile), ClipboardFileEnabled(bool), @@ -419,7 +419,8 @@ async fn handle(data: Data, stream: &mut Connection) { let t = Config::get_nat_type(); allow_err!(stream.send(&Data::NatType(Some(t))).await); } - Data::SyncConfig(Some((config, config2))) => { + Data::SyncConfig(Some(configs)) => { + let (config, config2) = *configs; let _chk = CheckIfRestart::new(); Config::set(config); Config2::set(config2); @@ -428,7 +429,9 @@ async fn handle(data: Data, stream: &mut Connection) { Data::SyncConfig(None) => { allow_err!( stream - .send(&Data::SyncConfig(Some((Config::get(), Config2::get())))) + .send(&Data::SyncConfig(Some( + (Config::get(), Config2::get()).into() + ))) .await ); } @@ -840,6 +843,19 @@ pub async fn test_rendezvous_server() -> ResultType<()> { #[tokio::main(flavor = "current_thread")] pub async fn send_url_scheme(url: String) -> ResultType<()> { - connect(1_000, "_url").await?.send(&Data::UrlLink(url)).await?; + connect(1_000, "_url") + .await? + .send(&Data::UrlLink(url)) + .await?; Ok(()) } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn verify_ffi_enum_data_size() { + println!("{}", std::mem::size_of::()); + assert!(std::mem::size_of::() < 96); + } +} diff --git a/src/keyboard.rs b/src/keyboard.rs index 91480ba30..17c52abf7 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -212,7 +212,7 @@ pub fn start_grab_loop() { } let mut _keyboard_mode = KeyboardMode::Map; - let scan_code = event.scan_code; + let _scan_code = event.scan_code; let res = if KEYBOARD_HOOKED.load(Ordering::SeqCst) { _keyboard_mode = client::process_event(&event, None); if is_press { @@ -225,7 +225,7 @@ pub fn start_grab_loop() { }; #[cfg(target_os = "windows")] - match scan_code { + match _scan_code { 0x1D | 0x021D => rdev::set_modifier(Key::ControlLeft, is_press), 0xE01D => rdev::set_modifier(Key::ControlRight, is_press), 0x2A => rdev::set_modifier(Key::ShiftLeft, is_press), @@ -241,7 +241,7 @@ pub fn start_grab_loop() { #[cfg(target_os = "windows")] unsafe { // AltGr - if scan_code == 0x021D { + if _scan_code == 0x021D { IS_0X021D_DOWN = is_press; } } diff --git a/src/server.rs b/src/server.rs index 616d92375..7807c4fac 100644 --- a/src/server.rs +++ b/src/server.rs @@ -8,6 +8,9 @@ use std::{ use bytes::Bytes; pub use connection::*; +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use hbb_common::config::Config2; +use hbb_common::tcp::new_listener; use hbb_common::{ allow_err, anyhow::{anyhow, Context}, @@ -17,18 +20,15 @@ use hbb_common::{ message_proto::*, protobuf::{Enum, Message as _}, rendezvous_proto::*, - ResultType, socket_client, - sodiumoxide::crypto::{box_, secretbox, sign}, Stream, timeout, tokio, + sodiumoxide::crypto::{box_, secretbox, sign}, + timeout, tokio, ResultType, Stream, }; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use hbb_common::config::Config2; -use hbb_common::tcp::new_listener; -use service::{GenericService, Service, Subscriber}; -#[cfg(not(any(target_os = "android", target_os = "ios")))] use service::ServiceTmpl; +use service::{GenericService, Service, Subscriber}; -use crate::ipc::{connect, Data}; +use crate::ipc::Data; pub mod audio_service; cfg_if::cfg_if! { @@ -65,7 +65,7 @@ type ConnMap = HashMap; lazy_static::lazy_static! { pub static ref CHILD_PROCESS: Childs = Default::default(); pub static ref CONN_COUNT: Arc> = Default::default(); - // A client server used to provide local services(audio, video, clipboard, etc.) + // A client server used to provide local services(audio, video, clipboard, etc.) // for all initiative connections. // // [Note] @@ -420,7 +420,8 @@ pub async fn start_server(is_server: bool) { if conn.send(&Data::SyncConfig(None)).await.is_ok() { if let Ok(Some(data)) = conn.next_timeout(1000).await { match data { - Data::SyncConfig(Some((config, config2))) => { + Data::SyncConfig(Some(configs)) => { + let (config, config2) = *configs; if Config::set(config) { log::info!("config synced"); } @@ -450,28 +451,26 @@ pub async fn start_ipc_url_server() { while let Some(Ok(conn)) = incoming.next().await { let mut conn = crate::ipc::Connection::new(conn); match conn.next_timeout(1000).await { - Ok(Some(data)) => { - match data { - Data::UrlLink(url) => { - #[cfg(feature = "flutter")] - { - if let Some(stream) = crate::flutter::GLOBAL_EVENT_STREAM.read().unwrap().get( - crate::flutter::APP_TYPE_MAIN - ) { - let mut m = HashMap::new(); - m.insert("name", "on_url_scheme_received"); - m.insert("url", url.as_str()); - stream.add(serde_json::to_string(&m).unwrap()); - } else { - log::warn!("No main window app found!"); - } - } - } - _ => { - log::warn!("An unexpected data was sent to the ipc url server.") + Ok(Some(data)) => match data { + #[cfg(feature = "flutter")] + Data::UrlLink(url) => { + if let Some(stream) = crate::flutter::GLOBAL_EVENT_STREAM + .read() + .unwrap() + .get(crate::flutter::APP_TYPE_MAIN) + { + let mut m = HashMap::new(); + m.insert("name", "on_url_scheme_received"); + m.insert("url", url.as_str()); + stream.add(serde_json::to_string(&m).unwrap()); + } else { + log::warn!("No main window app found!"); } } - } + _ => { + log::warn!("An unexpected data was sent to the ipc url server.") + } + }, Err(err) => { log::error!("{}", err); } @@ -509,7 +508,8 @@ async fn sync_and_watch_config_dir() { if conn.send(&Data::SyncConfig(None)).await.is_ok() { if let Ok(Some(data)) = conn.next_timeout(1000).await { match data { - Data::SyncConfig(Some((config, config2))) => { + Data::SyncConfig(Some(configs)) => { + let (config, config2) = *configs; let _chk = crate::ipc::CheckIfRestart::new(); if cfg0.0 != config { cfg0.0 = config.clone(); @@ -534,7 +534,7 @@ async fn sync_and_watch_config_dir() { let cfg = (Config::get(), Config2::get()); if cfg != cfg0 { log::info!("config updated, sync to root"); - match conn.send(&Data::SyncConfig(Some(cfg.clone()))).await { + match conn.send(&Data::SyncConfig(Some(cfg.clone().into()))).await { Err(e) => { log::error!("sync config to root failed: {}", e); break; diff --git a/src/ui/macos.rs b/src/ui/macos.rs index 98e355dc1..f34b7c2c1 100644 --- a/src/ui/macos.rs +++ b/src/ui/macos.rs @@ -14,12 +14,9 @@ use objc::{ sel, sel_impl, }; use objc::runtime::Class; -use objc_id::WeakId; use sciter::{Host, make_args}; -use hbb_common::{log, tokio}; - -use crate::ui_cm_interface::start_ipc; +use hbb_common::log; static APP_HANDLER_IVAR: &str = "GoDeskAppHandler"; @@ -141,7 +138,7 @@ extern "C" fn application_should_handle_open_untitled_file( if !LAUNCHED { return YES; } - hbb_common::log::debug!("icon clicked on finder"); + log::debug!("icon clicked on finder"); if std::env::args().nth(1) == Some("--server".to_owned()) { crate::platform::macos::check_main_window(); } @@ -267,4 +264,4 @@ pub fn make_tray() { set_delegate(None); } crate::tray::make_tray(); -} \ No newline at end of file +} diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index ccddab0ee..de33b0169 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -845,6 +845,7 @@ pub fn elevate_portable(_id: i32) { } } +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn handle_incoming_voice_call(id: i32, accept: bool) { if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { @@ -852,9 +853,10 @@ pub fn handle_incoming_voice_call(id: i32, accept: bool) { }; } +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn close_voice_call(id: i32) { if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { allow_err!(client.tx.send(Data::CloseVoiceCall("".to_owned()))); }; -} \ No newline at end of file +} From 1588e44d61b255ed3eb99529e90dd7e18e4779d9 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 19:17:43 +0800 Subject: [PATCH 113/117] win, translate mode, fix dead key Signed-off-by: fufesou --- Cargo.lock | 2 +- src/keyboard.rs | 21 +++++++++++++-------- src/ui_session_interface.rs | 6 +++--- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c53c573f2..83f623ca7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4405,7 +4405,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#4d8231f05e14c5a04cd7d2c1288e87ad52d39e4c" +source = "git+https://github.com/fufesou/rdev#cedc4e62744566775026af4b434ef799804c1130" dependencies = [ "cocoa", "core-foundation 0.9.3", diff --git a/src/keyboard.rs b/src/keyboard.rs index 17c52abf7..5b9920714 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -193,8 +193,8 @@ pub mod client { #[cfg(windows)] pub fn update_grab_get_key_name() { match get_keyboard_mode_enum() { - KeyboardMode::Map => rdev::set_get_key_name(false), - KeyboardMode::Translate => rdev::set_get_key_name(true), + KeyboardMode::Map => rdev::set_get_key_unicode(false), + KeyboardMode::Translate => rdev::set_get_key_unicode(true), _ => {} }; } @@ -256,6 +256,7 @@ pub fn start_grab_loop() { if let Err(error) = rdev::grab(func) { log::error!("rdev Error: {:?}", error) } + rdev::set_event_popup(false); }); #[cfg(target_os = "linux")] @@ -757,12 +758,10 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option) { match &event.unicode { Some(unicode_info) => { - if !unicode_info.is_dead { - for code in &unicode_info.unicode { - let mut evt = key_event.clone(); - evt.set_unicode(*code as _); - events.push(evt); - } + for code in &unicode_info.unicode { + let mut evt = key_event.clone(); + evt.set_unicode(*code as _); + events.push(evt); } } None => {} @@ -816,6 +815,12 @@ pub fn translate_virtual_keycode(event: &Event, mut key_event: KeyEvent) -> Opti pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec { let mut events: Vec = Vec::new(); + if let Some(unicode_info) = &event.unicode { + if unicode_info.is_dead { + return events; + } + } + #[cfg(target_os = "windows")] unsafe { if event.scan_code == 0x021D { diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index dc0e365ab..87ea8e9eb 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -368,8 +368,8 @@ impl Session { #[cfg(target_os = "windows")] { match &self.lc.read().unwrap().keyboard_mode as _ { - "legacy" => rdev::set_get_key_name(true), - "translate" => rdev::set_get_key_name(true), + "legacy" => rdev::set_get_key_unicode(true), + "translate" => rdev::set_get_key_unicode(true), _ => {} } } @@ -381,7 +381,7 @@ impl Session { pub fn leave(&self) { #[cfg(target_os = "windows")] { - rdev::set_get_key_name(false); + rdev::set_get_key_unicode(false); } IS_IN.store(false, Ordering::SeqCst); keyboard::client::change_grab_status(GrabState::Wait); From c049e728fd3f49db3b99338c377131294ce90473 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 19:25:25 +0800 Subject: [PATCH 114/117] suppress warns Signed-off-by: fufesou --- src/flutter_ffi.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 2e6c450c1..bb1b8b8b9 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -928,7 +928,7 @@ pub fn main_start_dbus_server() { { use crate::dbus::start_dbus_server; // spawn new thread to start dbus server - std::thread::spawn(|| { + thread::spawn(|| { let _ = start_dbus_server(); }); } @@ -1275,7 +1275,7 @@ pub fn main_is_login_wayland() -> SyncReturn { pub fn main_start_pa() { #[cfg(target_os = "linux")] - std::thread::spawn(crate::ipc::start_pa); + thread::spawn(crate::ipc::start_pa); } pub fn main_hide_docker() -> SyncReturn { @@ -1302,9 +1302,9 @@ pub fn main_start_ipc_url_server() { /// /// * macOS only #[allow(unused_variables)] -pub fn send_url_scheme(url: String) { +pub fn send_url_scheme(_url: String) { #[cfg(target_os = "macos")] - thread::spawn(move || crate::ui::macos::handle_url_scheme(url)); + thread::spawn(move || crate::ui::macos::handle_url_scheme(_url)); } #[cfg(target_os = "android")] @@ -1324,7 +1324,7 @@ pub mod server_side { _class: JClass, ) { log::debug!("startServer from java"); - std::thread::spawn(move || start_server(true)); + thread::spawn(move || start_server(true)); } #[no_mangle] From 3a0137a3f71bef19fa3a0e440f3025c860cfcaf3 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 8 Feb 2023 19:45:15 +0800 Subject: [PATCH 115/117] suppress warns Signed-off-by: fufesou --- src/flutter_ffi.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index bb1b8b8b9..ec4a90973 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1,6 +1,9 @@ -use std::{collections::HashMap, ffi::{CStr, CString}, os::raw::c_char, thread}; +use std::{collections::HashMap, ffi::{CStr, CString}, os::raw::c_char}; use std::str::FromStr; +#[cfg(any(target_os = "linux", target_os = "macos"))] +use std::thread; + use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer}; use serde_json::json; From 2feed1cdaf26c0f1a0f4f07819bfcb86b0e3934e Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 8 Feb 2023 20:00:16 +0800 Subject: [PATCH 116/117] though this change exe name to rustdesk, but it also change the name used in the other place --- flutter/macos/Runner.xcodeproj/project.pbxproj | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/flutter/macos/Runner.xcodeproj/project.pbxproj b/flutter/macos/Runner.xcodeproj/project.pbxproj index 18166c8ff..066560203 100644 --- a/flutter/macos/Runner.xcodeproj/project.pbxproj +++ b/flutter/macos/Runner.xcodeproj/project.pbxproj @@ -64,7 +64,7 @@ 295AD07E63F13855C270A0E0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* rustdesk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = rustdesk.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* RustDesk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RustDesk.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -127,7 +127,7 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* rustdesk.app */, + 33CC10ED2044A3C60003C045 /* RustDesk.app */, ); name = Products; sourceTree = ""; @@ -212,7 +212,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* rustdesk.app */; + productReference = 33CC10ED2044A3C60003C045 /* RustDesk.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -462,7 +462,6 @@ ); MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk; - PRODUCT_NAME = rustdesk; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -608,7 +607,6 @@ ); MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk; - PRODUCT_NAME = rustdesk; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; "SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h; @@ -646,7 +644,6 @@ /dev/null, ); PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk; - PRODUCT_NAME = rustdesk; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; "SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h; From 80da209be8226a0af25b64511e6c35f36cfb8829 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 8 Feb 2023 20:09:07 +0800 Subject: [PATCH 117/117] change executable name from RustDesk to rustdesk in mac deployment --- .github/workflows/flutter-nightly.yml | 1 - build.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/flutter-nightly.yml b/.github/workflows/flutter-nightly.yml index 5ca284cee..f03cd0be8 100644 --- a/.github/workflows/flutter-nightly.yml +++ b/.github/workflows/flutter-nightly.yml @@ -242,7 +242,6 @@ jobs: security unlock-keychain -p ${{ secrets.MACOS_P12_PASSWORD }} rustdesk.keychain # start sign the rustdesk.app and dmg rm rustdesk-${{ env.VERSION }}.dmg || true - mv ./flutter/build/macos/Build/Products/Release/rustdesk.app ./flutter/build/macos/Build/Products/Release/RustDesk.app codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep ./flutter/build/macos/Build/Products/Release/RustDesk.app -v create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep rustdesk-${{ env.VERSION }}.dmg -v diff --git a/build.py b/build.py index 6b107ff4b..dce434720 100755 --- a/build.py +++ b/build.py @@ -322,8 +322,9 @@ def build_flutter_dmg(version, features): os.system('sed -i "" "s/char \*\*rustdesk_core_main(int \*args_len);//" flutter/macos/Runner/bridge_generated.h') os.chdir('flutter') os.system('flutter build macos --release') + os.system('mv ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/RustDesk ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/rustdesk') os.system( - "create-dmg rustdesk.dmg ./build/macos/Build/Products/Release/rustdesk.app") + "create-dmg rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app") os.rename("rustdesk.dmg", f"../rustdesk-{version}.dmg") os.chdir("..")