Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	Cargo.lock
#	src/server/connection.rs
This commit is contained in:
mcfans
2023-11-07 12:13:15 +08:00
166 changed files with 10361 additions and 6739 deletions

View File

@@ -104,7 +104,7 @@ flutter {
dependencies {
implementation "androidx.media:media:1.6.0"
implementation 'com.github.getActivity:XXPermissions:16.2'
implementation 'com.github.getActivity:XXPermissions:18.5'
implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("$kotlin_version") } }
}

View File

@@ -61,6 +61,14 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Intent for deep linking-->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="rustdesk" />
</intent-filter>
</activity>
<activity
@@ -81,4 +89,4 @@
android:value="2" />
</application>
</manifest>
</manifest>

View File

@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '1.9.10'
repositories {
google()
jcenter()

View File

@@ -55,7 +55,7 @@ PODS:
- SDWebImage (5.15.5):
- SDWebImage/Core (= 5.15.5)
- SDWebImage/Core (5.15.5)
- sqflite (0.0.2):
- sqflite (0.0.3):
- Flutter
- FMDB (>= 2.7.5)
- SwiftyGif (5.4.4)
@@ -65,7 +65,8 @@ PODS:
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- wakelock (0.0.1):
- FlutterMacOS
- wakelock_plus (0.0.1):
- Flutter
DEPENDENCIES:
@@ -80,8 +81,8 @@ DEPENDENCIES:
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
trunk:
@@ -116,30 +117,30 @@ EXTERNAL SOURCES:
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
url_launcher_ios: 68d46cc9766d0c41dbdc884310529557e3cd7a86
video_player_avfoundation: 8563f13d8fc8b2c29dc2d09e60b660e4e8128837
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
PODFILE CHECKSUM: 2aff76ba0ac13439479560d1d03e9b4479f5c9e1

View File

@@ -159,7 +159,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -434,7 +434,7 @@
"-framework",
"\"video_player_avfoundation\"",
"-framework",
"\"wakelock\"",
"\"wakelock_plus\"",
);
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -632,7 +632,7 @@
"-framework",
"\"video_player_avfoundation\"",
"-framework",
"\"wakelock\"",
"\"wakelock_plus\"",
);
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -722,7 +722,7 @@
"-framework",
"\"video_player_avfoundation\"",
"-framework",
"\"wakelock\"",
"\"wakelock_plus\"",
);
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -24,6 +24,21 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLIconFile</key>
<string></string>
<key>CFBundleURLName</key>
<string>com.carriez.rustdesk</string>
<key>CFBundleURLSchemes</key>
<array>
<string>rustdesk</string>
</array>
</dict>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>

View File

@@ -1955,6 +1955,7 @@ bool handleUriLink({List<String>? cmdArgs, Uri? uri, String? uriString}) {
List<String>? urlLinkToCmdArgs(Uri uri) {
String? command;
String? id;
final options = ["connect", "play", "file-transfer", "port-forward", "rdp"];
if (uri.authority.isEmpty &&
uri.path.split('').every((char) => char == '/')) {
return [];
@@ -1962,18 +1963,33 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
// For compatibility
command = '--connect';
id = uri.path.substring("/new/".length);
} else if (['connect', "play", 'file-transfer', 'port-forward', 'rdp']
.contains(uri.authority)) {
} else if (options.contains(uri.authority)) {
final optionIndex = options.indexOf(uri.authority);
command = '--${uri.authority}';
if (uri.path.length > 1) {
id = uri.path.substring(1);
}
if (isMobile && id != null) {
if (optionIndex == 0 || optionIndex == 1) {
connect(Get.context!, id);
} else if (optionIndex == 2) {
connect(Get.context!, id, isFileTransfer: true);
}
return null;
}
} else if (uri.authority.length > 2 && uri.path.length <= 1) {
// rustdesk://<connect-id>
command = '--connect';
id = uri.authority;
}
if (isMobile){
if (id != null){
connect(Get.context!, id);
return null;
}
}
List<String> args = List.empty(growable: true);
if (command != null && id != null) {
args.add(command);

View File

@@ -972,7 +972,7 @@ void showRestartRemoteDevice(PeerInfo pi, String id, SessionID sessionId,
title: Row(children: [
Icon(Icons.warning_rounded, color: Colors.redAccent, size: 28),
Flexible(
child: Text(translate("Restart Remote Device"))
child: Text(translate("Restart remote device"))
.paddingOnly(left: 10)),
]),
content: Text(
@@ -1248,25 +1248,41 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
double fpsInitValue = 30;
bool qualitySet = false;
bool fpsSet = false;
bool? direct;
try {
direct =
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
} catch (_) {}
bool hideFps = (await bind.mainIsUsingPublicServer() && direct != true) ||
versionCmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
bool hideMoreQuality =
(await bind.mainIsUsingPublicServer() && direct != true) ||
versionCmp(ffi.ffiModel.pi.version, '1.2.2') < 0;
setCustomValues({double? quality, double? fps}) async {
if (quality != null) {
qualitySet = true;
await bind.sessionSetCustomImageQuality(
sessionId: sessionId, value: quality.toInt());
print("quality:$quality");
}
if (fps != null) {
fpsSet = true;
await bind.sessionSetCustomFps(sessionId: sessionId, fps: fps.toInt());
print("fps:$fps");
}
if (!qualitySet) {
qualitySet = true;
await bind.sessionSetCustomImageQuality(
sessionId: sessionId, value: qualityInitValue.toInt());
print("qualityInitValue:$qualityInitValue");
}
if (!fpsSet) {
if (!hideFps && !fpsSet) {
fpsSet = true;
await bind.sessionSetCustomFps(
sessionId: sessionId, fps: fpsInitValue.toInt());
print("fpsInitValue:$fpsInitValue");
}
}
@@ -1279,7 +1295,9 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
final quality = await bind.sessionGetCustomImageQuality(sessionId: sessionId);
qualityInitValue =
quality != null && quality.isNotEmpty ? quality[0].toDouble() : 50.0;
if (qualityInitValue < 10 || qualityInitValue > 2000) {
if ((hideMoreQuality && qualityInitValue > 100) ||
qualityInitValue < 10 ||
qualityInitValue > 2000) {
qualityInitValue = 50;
}
// fps
@@ -1289,20 +1307,14 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
if (fpsInitValue < 5 || fpsInitValue > 120) {
fpsInitValue = 30;
}
bool? direct;
try {
direct =
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
} catch (_) {}
bool notShowFps = (await bind.mainIsUsingPublicServer() && direct != true) ||
versionCmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
final content = customImageQualityWidget(
initQuality: qualityInitValue,
initFps: fpsInitValue,
setQuality: (v) => setCustomValues(quality: v),
setFps: (v) => setCustomValues(fps: v),
showFps: !notShowFps);
showFps: !hideFps,
showMoreQuality: !hideMoreQuality);
msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]);
}

View File

@@ -27,45 +27,44 @@ class DraggableChatWindow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return isIOS
? IOSDraggable (
position: position,
chatModel: chatModel,
width: width,
height: height,
builder: (context) {
return Column(
children: [
_buildMobileAppBar(context),
Expanded(
child: ChatPage(chatModel: chatModel),
),
],
);
},
)
: Draggable(
checkKeyboard: true,
position: position,
width: width,
height: height,
chatModel: chatModel,
builder: (context, onPanUpdate) {
final child =
Scaffold(
resizeToAvoidBottomInset: false,
appBar: CustomAppBar(
onPanUpdate: onPanUpdate,
appBar: isDesktop
? _buildDesktopAppBar(context)
: _buildMobileAppBar(context),
? IOSDraggable(
position: position,
chatModel: chatModel,
width: width,
height: height,
builder: (context) {
return Column(
children: [
_buildMobileAppBar(context),
Expanded(
child: ChatPage(chatModel: chatModel),
),
body: ChatPage(chatModel: chatModel),
);
return Container(
decoration:
BoxDecoration(border: Border.all(color: MyTheme.border)),
child: child);
});
],
);
},
)
: Draggable(
checkKeyboard: true,
position: position,
width: width,
height: height,
chatModel: chatModel,
builder: (context, onPanUpdate) {
final child = Scaffold(
resizeToAvoidBottomInset: false,
appBar: CustomAppBar(
onPanUpdate: onPanUpdate,
appBar: isDesktop
? _buildDesktopAppBar(context)
: _buildMobileAppBar(context),
),
body: ChatPage(chatModel: chatModel),
);
return Container(
decoration:
BoxDecoration(border: Border.all(color: MyTheme.border)),
child: child);
});
}
Widget _buildMobileAppBar(BuildContext context) {
@@ -354,14 +353,14 @@ class _DraggableState extends State<Draggable> {
}
class IOSDraggable extends StatefulWidget {
const IOSDraggable({
Key? key,
this.position = Offset.zero,
this.chatModel,
required this.width,
required this.height,
required this.builder})
: super(key: key);
const IOSDraggable(
{Key? key,
this.position = Offset.zero,
this.chatModel,
required this.width,
required this.height,
required this.builder})
: super(key: key);
final Offset position;
final ChatModel? chatModel;
@@ -423,7 +422,7 @@ class _IOSDraggableState extends State<IOSDraggable> {
_lastBottomHeight = bottomHeight;
}
@override
@override
Widget build(BuildContext context) {
checkKeyboard();
return Stack(
@@ -439,12 +438,12 @@ class _IOSDraggableState extends State<IOSDraggable> {
_chatModel?.setChatWindowPosition(_position);
},
child: Material(
child:
Container(
width: _width,
height: _height,
decoration: BoxDecoration(border: Border.all(color: MyTheme.border)),
child: widget.builder(context),
child: Container(
width: _width,
height: _height,
decoration:
BoxDecoration(border: Border.all(color: MyTheme.border)),
child: widget.builder(context),
),
),
),
@@ -499,6 +498,7 @@ class QualityMonitor extends StatelessWidget {
"${qualityMonitorModel.data.targetBitrate ?? '-'}kb"),
_row(
"Codec", qualityMonitorModel.data.codecFormat ?? '-'),
_row("Chroma", qualityMonitorModel.data.chroma ?? '-'),
],
),
)

View File

@@ -495,7 +495,7 @@ abstract class BasePeerCard extends StatelessWidget {
return _connectCommonAction(
context,
id,
translate('Transfer File'),
translate('Transfer file'),
isFileTransfer: true,
);
}
@@ -505,7 +505,7 @@ abstract class BasePeerCard extends StatelessWidget {
return _connectCommonAction(
context,
id,
translate('TCP Tunneling'),
translate('TCP tunneling'),
isTcpTunneling: true,
);
}
@@ -568,7 +568,7 @@ abstract class BasePeerCard extends StatelessWidget {
MenuEntryBase<String> _createShortCutAction(String id) {
return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Create Desktop Shortcut'),
translate('Create desktop shortcut'),
style: style,
),
proc: () {
@@ -818,7 +818,7 @@ abstract class BasePeerCard extends StatelessWidget {
MenuEntryBase<String> _addToAb(Peer peer) {
return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Add to Address Book'),
translate('Add to address book'),
style: style,
),
proc: () {

View File

@@ -75,9 +75,11 @@ class _PeerTabPageState extends State<PeerTabPage>
void initState() {
final uiType = bind.getLocalFlutterOption(k: 'peer-card-ui-type');
if (uiType != '') {
peerCardUiType.value = int.parse(uiType) == PeerUiType.list.index
? PeerUiType.list
: PeerUiType.grid;
peerCardUiType.value = int.parse(uiType) == 0
? PeerUiType.grid
: int.parse(uiType) == 1
? PeerUiType.tile
: PeerUiType.list;
}
hideAbTagsPanel.value =
bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty;
@@ -454,7 +456,7 @@ class _PeerTabPageState extends State<PeerTabPage>
});
},
child: Tooltip(
message: translate('Add to Address Book'),
message: translate('Add to address book'),
child: Icon(model.icons[PeerTabIndex.ab.index])),
).marginOnly(left: isMobile ? 11 : 6),
);
@@ -763,8 +765,6 @@ class PeerViewDropdown extends StatefulWidget {
}
class _PeerViewDropdownState extends State<PeerViewDropdown> {
RelativeRect menuPos = RelativeRect.fromLTRB(0, 0, 0, 0);
@override
Widget build(BuildContext context) {
final List<PeerUiType> types = [PeerUiType.grid, PeerUiType.tile, PeerUiType.list];
@@ -804,6 +804,7 @@ class _PeerViewDropdownState extends State<PeerViewDropdown> {
))));
}
var menuPos = RelativeRect.fromLTRB(0, 0, 0, 0);
return _hoverAction(
context: context,
child: Tooltip(
@@ -819,16 +820,14 @@ class _PeerViewDropdownState extends State<PeerViewDropdown> {
onTapDown: (details) {
final x = details.globalPosition.dx;
final y = details.globalPosition.dy;
setState(() {
menuPos = RelativeRect.fromLTRB(x, y, x, y);
});
menuPos = RelativeRect.fromLTRB(x, y, x, y);
},
onTap: () => showMenu(
context: context,
position: menuPos,
items: items,
elevation: 8,
),
)
);
}
}

View File

@@ -10,7 +10,11 @@ customImageQualityWidget(
required double initFps,
required Function(double) setQuality,
required Function(double) setFps,
required bool showFps}) {
required bool showFps,
required bool showMoreQuality}) {
if (!showMoreQuality && initQuality > 100) {
initQuality = 50;
}
final qualityValue = initQuality.obs;
final fpsValue = initFps.obs;
@@ -69,7 +73,7 @@ customImageQualityWidget(
style: const TextStyle(fontSize: 15),
)),
// mobile doesn't have enough space
if (!isMobile)
if (showMoreQuality && !isMobile)
Expanded(
flex: 1,
child: Row(
@@ -85,7 +89,7 @@ customImageQualityWidget(
))
],
)),
if (isMobile)
if (showMoreQuality && isMobile)
Obx(() => Row(
children: [
Expanded(
@@ -160,7 +164,8 @@ customImageQualitySetting() {
setFps: (v) {
bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString());
},
showFps: true);
showFps: true,
showMoreQuality: true);
}
Future<bool> setServerConfig(
@@ -265,7 +270,7 @@ List<Widget> ServerConfigImportExportWidgets(
return [
Tooltip(
message: translate('Import Server Config'),
message: translate('Import server config'),
child: IconButton(
icon: Icon(Icons.paste, color: Colors.grey), onPressed: import),
),

View File

@@ -133,7 +133,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
if (isDesktop) {
v.add(
TTextMenu(
child: Text(translate('Transfer File')),
child: Text(translate('Transfer file')),
onPressed: () => connect(context, id, isFileTransfer: true)),
);
}
@@ -141,7 +141,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
if (isDesktop) {
v.add(
TTextMenu(
child: Text(translate('TCP Tunneling')),
child: Text(translate('TCP tunneling')),
onPressed: () => connect(context, id, isTcpTunneling: true)),
);
}
@@ -176,7 +176,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
pi.platform == kPeerPlatformMacOS)) {
v.add(
TTextMenu(
child: Text(translate('Restart Remote Device')),
child: Text(translate('Restart remote device')),
onPressed: () =>
showRestartRemoteDevice(pi, id, sessionId, ffi.dialogManager)),
);
@@ -191,6 +191,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
}
// blockUserInput
if (ffi.ffiModel.keyboard &&
ffi.ffiModel.permissions['block_input'] != false &&
pi.platform == kPeerPlatformWindows) // privacy-mode != true ??
{
v.add(TTextMenu(
@@ -436,9 +437,9 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
child: Text(translate('Mute'))));
}
// file copy and paste
if (Platform.isWindows &&
pi.platform == kPeerPlatformWindows &&
perms['file'] != false) {
if (perms['file'] != false &&
bind.mainHasFileClipboard() &&
pi.platformAdditions.containsKey(kPlatformAdditionsHasFileClipboard)) {
final option = 'enable-file-transfer';
final value =
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
@@ -547,5 +548,22 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
child: Text(translate('Use all my displays for the remote session'))));
}
// 444
final codec_format = ffi.qualityMonitorModel.data.codecFormat;
if (versionCmp(pi.version, "1.2.4") >= 0 &&
(codec_format == "AV1" || codec_format == "VP9")) {
final option = 'i444';
final value =
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
v.add(TToggleMenu(
value: value,
onChanged: (value) async {
if (value == null) return;
await bind.sessionToggleOption(sessionId: sessionId, value: option);
bind.sessionChangePreferCodec(sessionId: sessionId);
},
child: Text(translate('True color (4:4:4)'))));
}
return v;
}

View File

@@ -22,6 +22,7 @@ const String kPlatformAdditionsIsWayland = "is_wayland";
const String kPlatformAdditionsHeadless = "headless";
const String kPlatformAdditionsIsInstalled = "is_installed";
const String kPlatformAdditionsVirtualDisplays = "virtual_displays";
const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
const String kPeerPlatformWindows = "Windows";
const String kPeerPlatformLinux = "Linux";

View File

@@ -50,6 +50,7 @@ class _ConnectionPageState extends State<ConnectionPage>
return list.sublist(0, n);
}
}
bool isPeersLoading = false;
bool isPeersLoaded = false;
@@ -81,7 +82,7 @@ class _ConnectionPageState extends State<ConnectionPage>
if (Get.isRegistered<IDTextEditingController>()) {
Get.delete<IDTextEditingController>();
}
if (Get.isRegistered<TextEditingController>()){
if (Get.isRegistered<TextEditingController>()) {
Get.delete<TextEditingController>();
}
super.dispose();
@@ -157,9 +158,9 @@ class _ConnectionPageState extends State<ConnectionPage>
await Future.delayed(Duration(milliseconds: 100));
peers = await getAllPeers();
setState(() {
isPeersLoading = false;
isPeersLoaded = true;
});
isPeersLoading = false;
isPeersLoaded = true;
});
}
/// UI for the remote ID TextField.
@@ -177,148 +178,173 @@ class _ConnectionPageState extends State<ConnectionPage>
Row(
children: [
Expanded(
child: AutoSizeText(
translate('Control Remote Desktop'),
maxLines: 1,
style: Theme.of(context)
.textTheme
.titleLarge
?.merge(TextStyle(height: 1)),
),
),
child: Row(
children: [
AutoSizeText(
translate('Control Remote Desktop'),
maxLines: 1,
style: Theme.of(context)
.textTheme
.titleLarge
?.merge(TextStyle(height: 1)),
).marginOnly(right: 4),
Tooltip(
waitDuration: Duration(milliseconds: 0),
message: translate("id_input_tip"),
child: Icon(
Icons.help_outline_outlined,
size: 16,
color: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5),
),
),
],
)),
],
).marginOnly(bottom: 15),
Row(
children: [
Expanded(
child:
Autocomplete<Peer>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<Peer>.empty();
}
else if (peers.isEmpty && !isPeersLoaded) {
Peer emptyPeer = Peer(
id: '',
username: '',
hostname: '',
alias: '',
platform: '',
tags: [],
hash: '',
forceAlwaysRelay: false,
rdpPort: '',
rdpUsername: '',
loginName: '',
child: Autocomplete<Peer>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<Peer>.empty();
} else if (peers.isEmpty && !isPeersLoaded) {
Peer emptyPeer = Peer(
id: '',
username: '',
hostname: '',
alias: '',
platform: '',
tags: [],
hash: '',
forceAlwaysRelay: false,
rdpPort: '',
rdpUsername: '',
loginName: '',
);
return [emptyPeer];
} else {
String textWithoutSpaces =
textEditingValue.text.replaceAll(" ", "");
if (int.tryParse(textWithoutSpaces) != null) {
textEditingValue = TextEditingValue(
text: textWithoutSpaces,
selection: textEditingValue.selection,
);
return [emptyPeer];
}
else {
String textWithoutSpaces = textEditingValue.text.replaceAll(" ", "");
if (int.tryParse(textWithoutSpaces) != null) {
textEditingValue = TextEditingValue(
text: textWithoutSpaces,
selection: textEditingValue.selection,
);
}
String textToFind = textEditingValue.text.toLowerCase();
String textToFind = textEditingValue.text.toLowerCase();
return peers.where((peer) =>
peer.id.toLowerCase().contains(textToFind) ||
peer.username.toLowerCase().contains(textToFind) ||
peer.hostname.toLowerCase().contains(textToFind) ||
peer.alias.toLowerCase().contains(textToFind))
.toList();
return peers
.where((peer) =>
peer.id.toLowerCase().contains(textToFind) ||
peer.username
.toLowerCase()
.contains(textToFind) ||
peer.hostname
.toLowerCase()
.contains(textToFind) ||
peer.alias.toLowerCase().contains(textToFind))
.toList();
}
},
fieldViewBuilder: (
BuildContext context,
TextEditingController fieldTextEditingController,
FocusNode fieldFocusNode,
VoidCallback onFieldSubmitted,
) {
fieldTextEditingController.text = _idController.text;
Get.put<TextEditingController>(fieldTextEditingController);
fieldFocusNode.addListener(() async {
_idInputFocused.value = fieldFocusNode.hasFocus;
if (fieldFocusNode.hasFocus && !isPeersLoading) {
_fetchPeers();
}
},
});
final textLength =
fieldTextEditingController.value.text.length;
// select all to facilitate removing text, just following the behavior of address input of chrome
fieldTextEditingController.selection =
TextSelection(baseOffset: 0, extentOffset: textLength);
return Obx(() => TextField(
autocorrect: false,
enableSuggestions: false,
keyboardType: TextInputType.visiblePassword,
focusNode: fieldFocusNode,
style: const TextStyle(
fontFamily: 'WorkSans',
fontSize: 22,
height: 1.4,
),
maxLines: 1,
cursorColor:
Theme.of(context).textTheme.titleLarge?.color,
decoration: InputDecoration(
filled: false,
counterText: '',
hintText: _idInputFocused.value
? null
: translate('Enter Remote ID'),
contentPadding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 13)),
controller: fieldTextEditingController,
inputFormatters: [IDTextInputFormatter()],
onChanged: (v) {
_idController.id = v;
},
));
},
onSelected: (option) {
setState(() {
_idController.id = option.id;
FocusScope.of(context).unfocus();
});
},
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<Peer> onSelected,
Iterable<Peer> options) {
double maxHeight = options.length * 50;
maxHeight = maxHeight > 200 ? 200 : maxHeight;
fieldViewBuilder: (BuildContext context,
TextEditingController fieldTextEditingController,
FocusNode fieldFocusNode ,
VoidCallback onFieldSubmitted,
) {
fieldTextEditingController.text = _idController.text;
Get.put<TextEditingController>(fieldTextEditingController);
fieldFocusNode.addListener(() async {
_idInputFocused.value = fieldFocusNode.hasFocus;
if (fieldFocusNode.hasFocus && !isPeersLoading){
_fetchPeers();
}
});
final textLength = fieldTextEditingController.value.text.length;
// select all to facilitate removing text, just following the behavior of address input of chrome
fieldTextEditingController.selection = TextSelection(baseOffset: 0, extentOffset: textLength);
return Obx(() =>
TextField(
maxLength: 90,
autocorrect: false,
enableSuggestions: false,
keyboardType: TextInputType.visiblePassword,
focusNode: fieldFocusNode,
style: const TextStyle(
fontFamily: 'WorkSans',
fontSize: 22,
height: 1.4,
),
maxLines: 1,
cursorColor: Theme.of(context).textTheme.titleLarge?.color,
decoration: InputDecoration(
filled: false,
counterText: '',
hintText: _idInputFocused.value
? null
: translate('Enter Remote ID'),
contentPadding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 13)),
controller: fieldTextEditingController,
inputFormatters: [IDTextInputFormatter()],
onChanged: (v) {
_idController.id = v;
},
));
},
onSelected: (option) {
setState(() {
_idController.id = option.id;
FocusScope.of(context).unfocus();
});
},
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<Peer> onSelected, Iterable<Peer> options) {
double maxHeight = options.length * 50;
maxHeight = maxHeight > 200 ? 200 : maxHeight;
return Align(
alignment: Alignment.topLeft,
child: ClipRRect(
return Align(
alignment: Alignment.topLeft,
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Material(
elevation: 4,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: maxHeight,
maxWidth: 319,
),
child: peers.isEmpty && isPeersLoading
? Container(
height: 80,
child: Center(
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
)
: Padding(
padding: const EdgeInsets.only(top: 5),
child: ListView(
children: options.map((peer) => AutocompletePeerTile(onSelect: () => onSelected(peer), peer: peer)).toList(),
elevation: 4,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: maxHeight,
maxWidth: 319,
),
child: peers.isEmpty && isPeersLoading
? Container(
height: 80,
child: Center(
child: CircularProgressIndicator(
strokeWidth: 2,
),
))
: Padding(
padding: const EdgeInsets.only(top: 5),
child: ListView(
children: options
.map((peer) => AutocompletePeerTile(
onSelect: () =>
onSelected(peer),
peer: peer))
.toList(),
),
),
),
),
)),
);
},
)
),
)),
);
},
)),
],
),
Padding(
@@ -329,7 +355,7 @@ class _ConnectionPageState extends State<ConnectionPage>
Button(
isOutline: true,
onTap: () => onConnect(isFileTransfer: true),
text: "Transfer File",
text: "Transfer file",
),
const SizedBox(
width: 17,
@@ -382,7 +408,7 @@ class _ConnectionPageState extends State<ConnectionPage>
onTap: () async {
await start_service(true);
},
child: Text(translate("Start Service"),
child: Text(translate("Start service"),
style: TextStyle(
decoration: TextDecoration.underline,
fontSize: em)))

View File

@@ -336,11 +336,17 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}
if (Platform.isWindows) {
if (!bind.mainIsInstalled()) {
return buildInstallCard(
"", "install_tip", "Install", bind.mainGotoInstall);
return buildInstallCard("", "install_tip", "Install", () async {
await rustDeskWinManager.closeAllSubWindows();
bind.mainGotoInstall();
});
} else if (bind.mainIsInstalledLowerVersion()) {
return buildInstallCard("Status", "Your installation is lower version.",
"Click to upgrade", bind.mainUpdateMe);
return buildInstallCard(
"Status", "Your installation is lower version.", "Click to upgrade",
() async {
await rustDeskWinManager.closeAllSubWindows();
bind.mainUpdateMe();
});
}
} else if (Platform.isMacOS) {
if (!bind.mainIsCanScreenRecording(prompt: false)) {
@@ -384,13 +390,16 @@ class _DesktopHomePageState extends State<DesktopHomePage>
final keyShowSelinuxHelpTip = "show-selinux-help-tip";
if (bind.mainGetLocalOption(key: keyShowSelinuxHelpTip) != 'N') {
LinuxCards.add(buildInstallCard(
"Warning", "selinux_tip", "", () async {},
marginTop: LinuxCards.isEmpty ? 20.0 : 5.0,
help: 'Help',
link:
'https://rustdesk.com/docs/en/client/linux/#permissions-issue',
closeButton: true,
closeOption: keyShowSelinuxHelpTip,
"Warning",
"selinux_tip",
"",
() async {},
marginTop: LinuxCards.isEmpty ? 20.0 : 5.0,
help: 'Help',
link:
'https://rustdesk.com/docs/en/client/linux/#permissions-issue',
closeButton: true,
closeOption: keyShowSelinuxHelpTip,
));
}
}
@@ -418,7 +427,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Widget buildInstallCard(String title, String content, String btnText,
GestureTapCallback onPressed,
{double marginTop = 20.0, String? help, String? link, bool? closeButton, String? closeOption}) {
{double marginTop = 20.0,
String? help,
String? link,
bool? closeButton,
String? closeOption}) {
void closeCard() async {
if (closeOption != null) {
await bind.mainSetLocalOption(key: closeOption, value: 'N');
@@ -439,89 +452,90 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Container(
margin: EdgeInsets.only(top: marginTop),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Color.fromARGB(255, 226, 66, 188),
Color.fromARGB(255, 244, 114, 124),
],
)),
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: (title.isNotEmpty
? <Widget>[
Center(
child: Text(
translate(title),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15),
).marginOnly(bottom: 6)),
]
: <Widget>[]) +
<Widget>[
Text(
translate(content),
style: TextStyle(
height: 1.5,
color: Colors.white,
fontWeight: FontWeight.normal,
fontSize: 13),
).marginOnly(bottom: 20)
] +
(btnText.isNotEmpty
? <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FixedWidthButton(
width: 150,
padding: 8,
isOutline: true,
text: translate(btnText),
textColor: Colors.white,
borderColor: Colors.white,
textSize: 20,
radius: 10,
onTap: onPressed,
)
])
]
: <Widget>[]) +
(help != null
? <Widget>[
Center(
child: InkWell(
onTap: () async =>
await launchUrl(Uri.parse(link!)),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Color.fromARGB(255, 226, 66, 188),
Color.fromARGB(255, 244, 114, 124),
],
)),
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: (title.isNotEmpty
? <Widget>[
Center(
child: Text(
translate(help),
style: TextStyle(
decoration: TextDecoration.underline,
color: Colors.white,
fontSize: 12),
)).marginOnly(top: 6)),
]
: <Widget>[]))),
translate(title),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15),
).marginOnly(bottom: 6)),
]
: <Widget>[]) +
<Widget>[
Text(
translate(content),
style: TextStyle(
height: 1.5,
color: Colors.white,
fontWeight: FontWeight.normal,
fontSize: 13),
).marginOnly(bottom: 20)
] +
(btnText.isNotEmpty
? <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FixedWidthButton(
width: 150,
padding: 8,
isOutline: true,
text: translate(btnText),
textColor: Colors.white,
borderColor: Colors.white,
textSize: 20,
radius: 10,
onTap: onPressed,
)
])
]
: <Widget>[]) +
(help != null
? <Widget>[
Center(
child: InkWell(
onTap: () async =>
await launchUrl(Uri.parse(link!)),
child: Text(
translate(help),
style: TextStyle(
decoration:
TextDecoration.underline,
color: Colors.white,
fontSize: 12),
)).marginOnly(top: 6)),
]
: <Widget>[]))),
),
if (closeButton != null && closeButton == true)
Positioned(
top: 18,
right: 0,
child: IconButton(
icon: Icon(
Icons.close,
color: Colors.white,
size: 20,
Positioned(
top: 18,
right: 0,
child: IconButton(
icon: Icon(
Icons.close,
color: Colors.white,
size: 20,
),
onPressed: closeCard,
),
onPressed: closeCard,
),
),
],
);
}

View File

@@ -632,23 +632,27 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
}).marginOnly(left: _kContentHMargin),
Column(
children: [
_OptionCheckBox(context, 'Enable Keyboard/Mouse', 'enable-keyboard',
_OptionCheckBox(context, 'Enable keyboard/mouse', 'enable-keyboard',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable Clipboard', 'enable-clipboard',
_OptionCheckBox(context, 'Enable clipboard', 'enable-clipboard',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(
context, 'Enable File Transfer', 'enable-file-transfer',
context, 'Enable file transfer', 'enable-file-transfer',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable Audio', 'enable-audio',
_OptionCheckBox(context, 'Enable audio', 'enable-audio',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable TCP Tunneling', 'enable-tunnel',
_OptionCheckBox(context, 'Enable TCP tunneling', 'enable-tunnel',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(
context, 'Enable Remote Restart', 'enable-remote-restart',
context, 'Enable remote restart', 'enable-remote-restart',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(
context, 'Enable Recording Session', 'enable-record-session',
context, 'Enable recording session', 'enable-record-session',
enabled: enabled, fakeValue: fakeValue),
if (Platform.isWindows)
_OptionCheckBox(
context, 'Enable blocking user input', 'enable-block-input',
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable remote configuration modification',
'allow-remote-config-modification',
enabled: enabled, fakeValue: fakeValue),
@@ -769,7 +773,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
bool enabled = !locked;
return _Card(title: 'Security', children: [
shareRdp(context, enabled),
_OptionCheckBox(context, 'Deny LAN Discovery', 'enable-lan-discovery',
_OptionCheckBox(context, 'Deny LAN discovery', 'enable-lan-discovery',
reverse: true, enabled: enabled),
...directIp(context),
whitelist(),
@@ -809,7 +813,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
update() => setState(() {});
RxBool applyEnabled = false.obs;
return [
_OptionCheckBox(context, 'Enable Direct IP Access', 'direct-server',
_OptionCheckBox(context, 'Enable direct IP access', 'direct-server',
update: update, enabled: !locked),
() {
// Simple temp wrapper for PR check
@@ -1320,6 +1324,7 @@ class _DisplayState extends State<_Display> {
otherRow('Lock after session end', 'lock_after_session_end'),
otherRow('Privacy mode', 'privacy_mode'),
otherRow('Reverse mouse wheel', 'reverse_mouse_wheel'),
otherRow('True color (4:4:4)', 'i444'),
];
if (useTextureRender) {
children.add(otherRow('Show displays as individual windows',

View File

@@ -15,7 +15,7 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/file_model.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../../consts.dart';
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
@@ -91,7 +91,7 @@ class _FileManagerPageState extends State<FileManagerPage>
});
Get.put(_ffi, tag: 'ft_${widget.id}');
if (!Platform.isLinux) {
Wakelock.enable();
WakelockPlus.enable();
}
debugPrint("File manager page init success with id ${widget.id}");
_ffi.dialogManager.setOverlayState(_overlayKeyState);
@@ -104,7 +104,7 @@ class _FileManagerPageState extends State<FileManagerPage>
_ffi.close();
_ffi.dialogManager.dismissAll();
if (!Platform.isLinux) {
Wakelock.disable();
WakelockPlus.disable();
}
Get.delete<FFI>(tag: 'ft_${widget.id}');
});
@@ -1126,10 +1126,11 @@ class _FileManagerViewState extends State<FileManagerView> {
void _onSelectedChanged(SelectedItems selectedItems, List<Entry> entries,
Entry entry, bool isLocal) {
final isCtrlDown = RawKeyboard.instance.keysPressed
.contains(LogicalKeyboardKey.controlLeft);
final isCtrlDown = RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.controlLeft) ||
RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.controlRight);
final isShiftDown =
RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftLeft);
RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftLeft) ||
RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftRight);
if (isCtrlDown) {
if (selectedItems.items.contains(entry)) {
selectedItems.remove(entry);

View File

@@ -8,7 +8,7 @@ import 'package:flutter_custom_cursor/cursor_manager.dart'
as custom_cursor_manager;
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
import 'package:flutter_improved_scrolling/flutter_improved_scrolling.dart';
@@ -123,7 +123,7 @@ class _RemotePageState extends State<RemotePage>
.showLoading(translate('Connecting...'), onCancel: closeConnection);
});
if (!Platform.isLinux) {
Wakelock.enable();
WakelockPlus.enable();
}
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
@@ -183,7 +183,7 @@ class _RemotePageState extends State<RemotePage>
_isWindowBlur = false;
}
if (!Platform.isLinux) {
Wakelock.enable();
WakelockPlus.enable();
}
}
@@ -192,7 +192,7 @@ class _RemotePageState extends State<RemotePage>
void onWindowMaximize() {
super.onWindowMaximize();
if (!Platform.isLinux) {
Wakelock.enable();
WakelockPlus.enable();
}
}
@@ -200,7 +200,7 @@ class _RemotePageState extends State<RemotePage>
void onWindowMinimize() {
super.onWindowMinimize();
if (!Platform.isLinux) {
Wakelock.disable();
WakelockPlus.disable();
}
}
@@ -228,7 +228,7 @@ class _RemotePageState extends State<RemotePage>
overlays: SystemUiOverlay.values);
}
if (!Platform.isLinux) {
await Wakelock.disable();
await WakelockPlus.disable();
}
await Get.delete<FFI>(tag: widget.id);
removeSharedStates(widget.id);

View File

@@ -386,7 +386,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
pi.platform == kPeerPlatformMacOS)) {
menu.add(MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Restart Remote Device'),
translate('Restart remote device'),
style: style,
),
proc: () => showRestartRemoteDevice(

View File

@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/cm_file_model.dart';
import 'package:flutter_hbb/utils/platform_channel.dart';
import 'package:get/get.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
@@ -482,8 +483,8 @@ class _CmHeaderState extends State<_CmHeader>
client.type_() != ClientType.file),
child: IconButton(
onPressed: () => checkClickTime(client.id, () {
if (client.type_() != ClientType.file) {
gFFI.chatModel.toggleCMSidePage();
if (client.type_() == ClientType.file) {
gFFI.chatModel.toggleCMFilePage();
} else {
gFFI.chatModel
.toggleCMChatPage(MessageKey(client.peerId, client.id));
@@ -519,6 +520,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
Function(bool)? onTap, String tooltipText) {
return Tooltip(
message: "$tooltipText: ${enabled ? "ON" : "OFF"}",
waitDuration: Duration.zero,
child: Container(
decoration: BoxDecoration(
color: enabled ? MyTheme.accent : Colors.grey[700],
@@ -535,7 +537,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
child: Icon(
iconData,
color: Colors.white,
size: 32,
),
),
],
@@ -547,9 +548,11 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
@override
Widget build(BuildContext context) {
final crossAxisCount = 4;
final spacing = 10.0;
return Container(
width: double.infinity,
height: 200.0,
height: 160.0,
margin: EdgeInsets.all(5.0),
padding: EdgeInsets.all(5.0),
decoration: BoxDecoration(
@@ -574,10 +577,10 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
).marginOnly(left: 4.0, bottom: 8.0),
Expanded(
child: GridView.count(
crossAxisCount: 3,
padding: EdgeInsets.symmetric(horizontal: 20.0),
mainAxisSpacing: 20.0,
crossAxisSpacing: 20.0,
crossAxisCount: crossAxisCount,
padding: EdgeInsets.symmetric(horizontal: spacing),
mainAxisSpacing: spacing,
crossAxisSpacing: spacing,
children: [
buildPermissionIcon(
client.keyboard,
@@ -589,7 +592,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.keyboard = enabled;
});
},
translate('Allow using keyboard and mouse'),
translate('Enable keyboard/mouse'),
),
buildPermissionIcon(
client.clipboard,
@@ -601,7 +604,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.clipboard = enabled;
});
},
translate('Allow using clipboard'),
translate('Enable clipboard'),
),
buildPermissionIcon(
client.audio,
@@ -613,7 +616,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.audio = enabled;
});
},
translate('Allow hearing sound'),
translate('Enable audio'),
),
buildPermissionIcon(
client.file,
@@ -625,7 +628,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.file = enabled;
});
},
translate('Allow file copy and paste'),
translate('Enable file copy and paste'),
),
buildPermissionIcon(
client.restart,
@@ -637,7 +640,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.restart = enabled;
});
},
translate('Allow remote restart'),
translate('Enable remote restart'),
),
buildPermissionIcon(
client.recording,
@@ -649,8 +652,24 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
client.recording = enabled;
});
},
translate('Allow recording session'),
)
translate('Enable recording session'),
),
// only windows support block input
if (Platform.isWindows)
buildPermissionIcon(
client.blockInput,
Icons.block,
(enabled) {
bind.cmSwitchPermission(
connId: client.id,
name: "block_input",
enabled: enabled);
setState(() {
client.blockInput = enabled;
});
},
translate('Enable blocking user input'),
)
],
),
),
@@ -975,6 +994,49 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
);
}
iconLabel(CmFileLog item) {
switch (item.action) {
case CmFileAction.none:
return Container();
case CmFileAction.localToRemote:
case CmFileAction.remoteToLocal:
return Column(
children: [
Transform.rotate(
angle: item.action == CmFileAction.remoteToLocal ? 0 : pi,
child: SvgPicture.asset(
"assets/arrow.svg",
color: Theme.of(context).tabBarTheme.labelColor,
),
),
Text(item.action == CmFileAction.remoteToLocal
? translate('Send')
: translate('Receive'))
],
);
case CmFileAction.remove:
return Column(
children: [
Icon(
Icons.delete,
color: Theme.of(context).tabBarTheme.labelColor,
),
Text(translate('Delete'))
],
);
case CmFileAction.createDir:
return Column(
children: [
Icon(
Icons.create_new_folder,
color: Theme.of(context).tabBarTheme.labelColor,
),
Text(translate('Create Folder'))
],
);
}
}
Widget statusList() {
return PreferredSize(
preferredSize: const Size(200, double.infinity),
@@ -983,7 +1045,7 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
child: Obx(
() {
final jobTable = gFFI.cmFileModel.currentJobTable;
statusListView(List<JobProgress> jobs) => ListView.builder(
statusListView(List<CmFileLog> jobs) => ListView.builder(
controller: ScrollController(),
itemBuilder: (BuildContext context, int index) {
final item = jobs[index];
@@ -998,22 +1060,7 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
children: [
SizedBox(
width: 50,
child: Column(
children: [
Transform.rotate(
angle: item.isRemoteToLocal ? 0 : pi,
child: SvgPicture.asset(
"assets/arrow.svg",
color: Theme.of(context)
.tabBarTheme
.labelColor,
),
),
Text(item.isRemoteToLocal
? translate('Send')
: translate('Receive'))
],
),
child: iconLabel(item),
).paddingOnly(left: 15),
const SizedBox(
width: 16.0,
@@ -1048,8 +1095,9 @@ class __FileTransferLogPageState extends State<_FileTransferLogPage> {
),
),
Offstage(
offstage:
item.state == JobState.inProgress,
offstage: !(item.isTransfer() &&
item.state !=
JobState.inProgress),
child: Text(
translate(
item.display(),

View File

@@ -107,7 +107,7 @@ class _ToolbarTheme {
static const double dividerHeight = 12.0;
static const double buttonSize = 32;
static const double buttonHMargin = 3;
static const double buttonHMargin = 2;
static const double buttonVMargin = 6;
static const double iconRadius = 8;
static const double elevation = 3;
@@ -125,12 +125,13 @@ class _ToolbarTheme {
: EdgeInsets.fromLTRB(6, 14, 6, 14);
static const double menuButtonBorderRadius = 3.0;
static get borderColor =>
MyTheme.currentThemeMode() == ThemeMode.light ? bordLight : bordDark;
static final defaultMenuStyle = MenuStyle(
side: MaterialStateProperty.all(BorderSide(
width: 1,
color: MyTheme.currentThemeMode() == ThemeMode.light
? _ToolbarTheme.bordLight
: _ToolbarTheme.bordDark,
color: borderColor,
)),
shape: MaterialStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(_ToolbarTheme.menuBorderRadius))),
@@ -141,6 +142,19 @@ class _ToolbarTheme {
padding: MaterialStatePropertyAll(EdgeInsets.zero),
overlayColor: MaterialStatePropertyAll(Colors.transparent),
);
static Widget borderWrapper(Widget child, BorderRadius borderRadius) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: borderColor,
width: 1,
),
borderRadius: borderRadius,
),
child: child,
);
}
}
typedef DismissFunc = void Function();
@@ -420,6 +434,9 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
if (show.isTrue && _dragging.isFalse) {
triggerAutoHide();
}
final borderRadius = BorderRadius.vertical(
bottom: Radius.circular(5),
);
return Align(
alignment: FractionalOffset(_fractionX.value, 0),
child: Offstage(
@@ -427,6 +444,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
child: Material(
elevation: _ToolbarTheme.elevation,
shadowColor: MyTheme.color(context).shadow,
borderRadius: borderRadius,
child: _DraggableShowHide(
sessionId: widget.ffi.sessionId,
dragging: _dragging,
@@ -434,6 +452,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
show: show,
setFullscreen: _setFullscreen,
setMinimize: _minimize,
borderRadius: borderRadius,
),
),
),
@@ -475,13 +494,14 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
}
toolbarItems.add(_RecordMenu());
toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi));
final toolbarBorderRadius = BorderRadius.all(Radius.circular(4.0));
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Material(
elevation: _ToolbarTheme.elevation,
shadowColor: MyTheme.color(context).shadow,
borderRadius: BorderRadius.all(Radius.circular(4.0)),
borderRadius: toolbarBorderRadius,
color: Theme.of(context)
.menuBarTheme
.style
@@ -491,13 +511,15 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
scrollDirection: Axis.horizontal,
child: Theme(
data: themeData(),
child: Row(
children: [
SizedBox(width: _ToolbarTheme.buttonHMargin * 2),
...toolbarItems,
SizedBox(width: _ToolbarTheme.buttonHMargin * 2)
],
),
child: _ToolbarTheme.borderWrapper(
Row(
children: [
SizedBox(width: _ToolbarTheme.buttonHMargin * 2),
...toolbarItems,
SizedBox(width: _ToolbarTheme.buttonHMargin * 2)
],
),
toolbarBorderRadius),
),
),
),
@@ -598,14 +620,19 @@ class _MonitorMenu extends StatelessWidget {
useTextureRender && ffi.ffiModel.pi.isSupportMultiDisplay;
@override
Widget build(BuildContext context) =>
showMonitorsToolbar ? buildMultiMonitorMenu() : buildMonitorMenu();
Widget build(BuildContext context) => showMonitorsToolbar
? buildMultiMonitorMenu()
: Obx(() => buildMonitorMenu());
Widget buildMonitorMenu() {
final width = SimpleWrapper<double>(0);
final monitorsIcon =
globalMonitorsWidget(width, Colors.white, Colors.black38);
return _IconSubmenuButton(
tooltip: 'Select Monitor',
icon: icon(),
icon: monitorsIcon,
ffi: ffi,
width: width.value,
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
menuStyle: MenuStyle(
@@ -644,26 +671,37 @@ class _MonitorMenu extends StatelessWidget {
child: Text(translate('Show displays as individual windows')));
}
buildOneMonitorButton(i, curDisplay) => Text(
'${i + 1}',
style: TextStyle(
color: i == curDisplay
? _ToolbarTheme.blueColor
: _ToolbarTheme.inactiveColor,
fontSize: 12,
fontWeight: FontWeight.bold,
),
);
List<Widget> buildMonitorList(bool isMulti) {
final List<Widget> monitorList = [];
final pi = ffi.ffiModel.pi;
getMonitorText(int i) {
if (i == kAllDisplayValue) {
if (pi.displays.length == 2) {
return '1|2';
} else {
return 'ALL';
}
} else {
return (i + 1).toString();
}
}
buildMonitorButton(int i) => Obx(() {
RxInt display = CurrentDisplayState.find(id);
final isAllMonitors = i == kAllDisplayValue;
final width = SimpleWrapper<double>(0);
Widget? monitorsIcon;
if (isAllMonitors) {
monitorsIcon = globalMonitorsWidget(
width, Colors.white, _ToolbarTheme.blueColor);
}
return _IconMenuButton(
tooltip: isMulti ? '' : '#${i + 1} monitor',
tooltip: isMulti
? ''
: isAllMonitors
? 'all monitors'
: '#${i + 1} monitor',
hMargin: isMulti ? null : 6,
vMargin: isMulti ? null : 12,
topLevel: false,
@@ -673,33 +711,25 @@ class _MonitorMenu extends StatelessWidget {
hoverColor: i == display.value
? _ToolbarTheme.hoverBlueColor
: _ToolbarTheme.hoverInactiveColor,
icon: Container(
alignment: AlignmentDirectional.center,
constraints:
const BoxConstraints(minHeight: _ToolbarTheme.height),
child: Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
"assets/screen.svg",
colorFilter:
ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Obx(
() => Text(
getMonitorText(i),
style: TextStyle(
color: i == display.value
? _ToolbarTheme.blueColor
: _ToolbarTheme.inactiveColor,
fontSize: 12,
fontWeight: FontWeight.bold,
),
width: isAllMonitors ? width.value : null,
icon: isAllMonitors
? monitorsIcon
: Container(
alignment: AlignmentDirectional.center,
constraints:
const BoxConstraints(minHeight: _ToolbarTheme.height),
child: Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
"assets/screen.svg",
colorFilter:
ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Obx(() => buildOneMonitorButton(i, display.value)),
],
),
),
],
),
),
onPressed: () => onPressed(i, pi),
);
});
@@ -713,26 +743,69 @@ class _MonitorMenu extends StatelessWidget {
return monitorList;
}
icon() {
final pi = ffi.ffiModel.pi;
globalMonitorsWidget(
SimpleWrapper<double> width, Color activeTextColor, Color activeBgColor) {
getMonitors() {
final pi = ffi.ffiModel.pi;
RxInt display = CurrentDisplayState.find(id);
final rect = ffi.ffiModel.globalDisplaysRect();
if (rect == null) {
return Offstage();
}
final scale = _ToolbarTheme.buttonSize / rect.height * 0.75;
final startY = (_ToolbarTheme.buttonSize - rect.height * scale) * 0.5;
final startX = startY;
final children = <Widget>[];
for (var i = 0; i < pi.displays.length; i++) {
final d = pi.displays[i];
final fontSize = (d.width * scale < d.height * scale
? d.width * scale
: d.height * scale) *
0.65;
children.add(Positioned(
left: (d.x - rect.left) * scale + startX,
top: (d.y - rect.top) * scale + startY,
width: d.width * scale,
height: d.height * scale,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey,
width: 1.0,
),
color: display.value == i ? activeBgColor : Colors.white,
),
child: Center(
child: Text(
'${i + 1}',
style: TextStyle(
color: display.value == i
? activeTextColor
: _ToolbarTheme.inactiveColor,
fontSize: fontSize,
fontWeight: FontWeight.bold,
),
)),
),
));
}
width.value = rect.width * scale + startX * 2;
return SizedBox(
width: width.value,
height: rect.height * scale + startY * 2,
child: Stack(
children: children,
),
);
}
return Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
"assets/screen.svg",
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Obx(() {
RxInt display = CurrentDisplayState.find(id);
return Text(
'${display.value == kAllDisplayValue ? 'A' : '${display.value + 1}'}/${pi.displays.length}',
style: const TextStyle(
color: _ToolbarTheme.blueColor,
fontSize: 8,
fontWeight: FontWeight.bold,
),
);
}),
SizedBox(height: _ToolbarTheme.buttonSize),
getMonitors(),
],
);
}
@@ -1150,8 +1223,9 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
@override
Widget build(BuildContext context) {
final isVirtualDisplay = ffiModel.isVirtualDisplayResolution;
final visible =
ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1);
final visible = ffiModel.keyboard &&
(isVirtualDisplay || resolutions.length > 1) &&
pi.currentDisplay != kAllDisplayValue;
if (!visible) return Offstage();
final showOriginalBtn =
ffiModel.isOriginalResolutionSet && !ffiModel.isOriginalResolution;
@@ -1761,6 +1835,7 @@ class _IconMenuButton extends StatefulWidget {
final double? hMargin;
final double? vMargin;
final bool topLevel;
final double? width;
const _IconMenuButton({
Key? key,
this.assetName,
@@ -1772,6 +1847,7 @@ class _IconMenuButton extends StatefulWidget {
this.hMargin,
this.vMargin,
this.topLevel = true,
this.width,
}) : super(key: key);
@override
@@ -1792,7 +1868,7 @@ class _IconMenuButtonState extends State<_IconMenuButton> {
height: _ToolbarTheme.buttonSize,
);
var button = SizedBox(
width: _ToolbarTheme.buttonSize,
width: widget.width ?? _ToolbarTheme.buttonSize,
height: _ToolbarTheme.buttonSize,
child: MenuItemButton(
style: ButtonStyle(
@@ -1839,18 +1915,20 @@ class _IconSubmenuButton extends StatefulWidget {
final List<Widget> menuChildren;
final MenuStyle? menuStyle;
final FFI ffi;
final double? width;
_IconSubmenuButton(
{Key? key,
this.svg,
this.icon,
required this.tooltip,
required this.color,
required this.hoverColor,
required this.menuChildren,
required this.ffi,
this.menuStyle})
: super(key: key);
_IconSubmenuButton({
Key? key,
this.svg,
this.icon,
required this.tooltip,
required this.color,
required this.hoverColor,
required this.menuChildren,
required this.ffi,
this.menuStyle,
this.width,
}) : super(key: key);
@override
State<_IconSubmenuButton> createState() => _IconSubmenuButtonState();
@@ -1870,7 +1948,7 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
height: _ToolbarTheme.buttonSize,
);
final button = SizedBox(
width: _ToolbarTheme.buttonSize,
width: widget.width ?? _ToolbarTheme.buttonSize,
height: _ToolbarTheme.buttonSize,
child: SubmenuButton(
menuStyle: widget.menuStyle ?? _ToolbarTheme.defaultMenuStyle,
@@ -2016,6 +2094,7 @@ class _DraggableShowHide extends StatefulWidget {
final RxDouble fractionX;
final RxBool dragging;
final RxBool show;
final BorderRadius borderRadius;
final Function(bool) setFullscreen;
final Function() setMinimize;
@@ -2028,6 +2107,7 @@ class _DraggableShowHide extends StatefulWidget {
required this.show,
required this.setFullscreen,
required this.setMinimize,
required this.borderRadius,
}) : super(key: key);
@override
@@ -2164,9 +2244,11 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
.style
?.backgroundColor
?.resolve(MaterialState.values.toSet()),
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(5),
border: Border.all(
color: _ToolbarTheme.borderColor,
width: 1,
),
borderRadius: widget.borderRadius,
),
child: SizedBox(
height: 20,

View File

@@ -583,32 +583,19 @@ class WindowActionPanelState extends State<WindowActionPanel>
void onWindowClose() async {
mainWindowClose() async => await windowManager.hide();
notMainWindowClose(WindowController controller) async {
if (widget.tabController.length == 0) {
debugPrint("close emtpy multiwindow, hide");
await controller.hide();
await rustDeskWinManager
.call(WindowType.Main, kWindowEventHide, {"id": kWindowId!});
} else {
if (widget.tabController.length != 0) {
debugPrint("close not emtpy multiwindow from taskbar");
if (Platform.isWindows) {
await controller.show();
await controller.focus();
final res = await widget.onClose?.call() ?? true;
if (res) {
Future.delayed(Duration.zero, () async {
// onWindowClose will be called again to hide
await WindowController.fromWindowId(kWindowId!).close();
});
}
} else {
// ubuntu22.04 windowOnTop not work from taskbar
widget.tabController.clear();
Future.delayed(Duration.zero, () async {
// onWindowClose will be called again to hide
await WindowController.fromWindowId(kWindowId!).close();
});
if (!res) return;
}
widget.tabController.clear();
}
await controller.hide();
await rustDeskWinManager
.call(WindowType.Main, kWindowEventHide, {"id": kWindowId!});
}
macOSWindowClose(

View File

@@ -156,6 +156,7 @@ void runMobileApp() async {
await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]);
gFFI.userModel.refreshCurrentUser();
runApp(App());
await initUniLinks();
}
void runMultiWindow(

View File

@@ -54,10 +54,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
}
bool isPeersLoading = false;
bool isPeersLoaded = false;
StreamSubscription? _uniLinksSubscription;
@override
void initState() {
super.initState();
_uniLinksSubscription = listenUniLinks();
if (_idController.text.isEmpty) {
() async {
final lastRemoteId = await bind.mainGetLastRemoteId();
@@ -312,6 +314,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
@override
void dispose() {
_uniLinksSubscription?.cancel();
_idController.dispose();
if (Get.isRegistered<IDTextEditingController>()) {
Get.delete<IDTextEditingController>();

View File

@@ -5,7 +5,7 @@ import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
import 'package:flutter_hbb/models/file_model.dart';
import 'package:get/get.dart';
import 'package:toggle_switch/toggle_switch.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../../common.dart';
import '../../common/widgets/dialog.dart';
@@ -73,7 +73,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
.showLoading(translate('Connecting...'), onCancel: closeConnection);
});
gFFI.ffiModel.updateEventListener(gFFI.sessionId, widget.id);
Wakelock.enable();
WakelockPlus.enable();
}
@override
@@ -81,7 +81,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
model.close().whenComplete(() {
gFFI.close();
gFFI.dialogManager.dismissAll();
Wakelock.disable();
WakelockPlus.disable();
});
super.dispose();
}

View File

@@ -11,7 +11,7 @@ import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../../common.dart';
import '../../common/widgets/overlay.dart';
@@ -60,7 +60,7 @@ class _RemotePageState extends State<RemotePage> {
gFFI.dialogManager
.showLoading(translate('Connecting...'), onCancel: closeConnection);
});
Wakelock.enable();
WakelockPlus.enable();
_physicalFocusNode.requestFocus();
gFFI.ffiModel.updateEventListener(sessionId, widget.id);
gFFI.inputModel.listenToMouse(true);
@@ -88,7 +88,7 @@ class _RemotePageState extends State<RemotePage> {
gFFI.dialogManager.dismissAll();
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: SystemUiOverlay.values);
await Wakelock.disable();
await WakelockPlus.disable();
await keyboardSubscription.cancel();
removeSharedStates(widget.id);
}

View File

@@ -217,7 +217,7 @@ class ServiceNotRunningNotification extends StatelessWidget {
serverModel.toggleService();
}
},
label: Text(translate("Start Service")))
label: Text(translate("Start service")))
],
));
}
@@ -561,7 +561,7 @@ class _PermissionCheckerState extends State<PermissionChecker> {
serverModel.toggleService),
PermissionRow(translate("Input Control"), serverModel.inputOk,
serverModel.toggleInput),
PermissionRow(translate("Transfer File"), serverModel.fileOk,
PermissionRow(translate("Transfer file"), serverModel.fileOk,
serverModel.toggleFile),
hasAudioPermission
? PermissionRow(translate("Audio Capture"), serverModel.audioOk,

View File

@@ -221,7 +221,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
final List<AbstractSettingsTile> enhancementsTiles = [];
final List<AbstractSettingsTile> shareScreenTiles = [
SettingsTile.switchTile(
title: Text(translate('Deny LAN Discovery')),
title: Text(translate('Deny LAN discovery')),
initialValue: _denyLANDiscovery,
onToggle: (v) async {
await bind.mainSetOption(
@@ -270,7 +270,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
},
),
SettingsTile.switchTile(
title: Text(translate('Enable Recording Session')),
title: Text(translate('Enable recording session')),
initialValue: _enableRecordSession,
onToggle: (v) async {
await bind.mainSetOption(
@@ -407,7 +407,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
enhancementsTiles.add(SettingsTile.switchTile(
initialValue: _enableStartOnBoot,
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text("${translate('Start on Boot')} (beta)"),
Text("${translate('Start on boot')} (beta)"),
Text(
'* ${translate('Start the screen sharing service on boot, requires special permissions')}',
style: Theme.of(context).textTheme.bodySmall),
@@ -797,6 +797,7 @@ class __DisplayPageState extends State<_DisplayPage> {
otherRow('Lock after session end', 'lock_after_session_end'),
otherRow('Privacy mode', 'privacy_mode'),
otherRow('Touch mode', 'touch-mode'),
otherRow('True color (4:4:4)', 'i444'),
],
),
]),

View File

@@ -285,6 +285,10 @@ class ChatModel with ChangeNotifier {
await toggleCMSidePage();
}
toggleCMFilePage() async {
await toggleCMSidePage();
}
var _togglingCMSidePage = false; // protect order for await
toggleCMSidePage() async {
if (_togglingCMSidePage) return false;
@@ -296,6 +300,13 @@ class ChatModel with ChangeNotifier {
await windowManager.setSizeAlignment(
kConnectionManagerWindowSizeClosedChat, Alignment.topRight);
} else {
final currentSelectedTab =
gFFI.serverModel.tabController.state.value.selectedTabInfo;
final client = parent.target?.serverModel.clients.firstWhereOrNull(
(client) => client.id.toString() == currentSelectedTab.key);
if (client != null) {
client.unreadChatMessageCount.value = 0;
}
requestChatInputFocus();
await windowManager.show();
await windowManager.setSizeAlignment(

View File

@@ -10,8 +10,8 @@ import 'file_model.dart';
class CmFileModel {
final WeakReference<FFI> parent;
final currentJobTable = RxList<JobProgress>();
final _jobTables = HashMap<int, RxList<JobProgress>>.fromEntries([]);
final currentJobTable = RxList<CmFileLog>();
final _jobTables = HashMap<int, RxList<CmFileLog>>.fromEntries([]);
Stopwatch stopwatch = Stopwatch();
int _lastElapsed = 0;
@@ -19,14 +19,24 @@ class CmFileModel {
void updateCurrentClientId(int id) {
if (_jobTables[id] == null) {
_jobTables[id] = RxList<JobProgress>();
_jobTables[id] = RxList<CmFileLog>();
}
Future.delayed(Duration.zero, () {
currentJobTable.value = _jobTables[id]!;
});
}
onFileTransferLog(dynamic log) {
onFileTransferLog(Map<String, dynamic> evt) {
if (evt['transfer'] != null) {
_onFileTransfer(evt['transfer']);
} else if (evt['remove'] != null) {
_onFileRemove(evt['remove']);
} else if (evt['create_dir'] != null) {
_onDirCreate(evt['create_dir']);
}
}
_onFileTransfer(dynamic log) {
try {
dynamic d = jsonDecode(log);
if (!stopwatch.isRunning) stopwatch.start();
@@ -56,9 +66,9 @@ class CmFileModel {
debugPrint("jobTable should not be null");
return;
}
JobProgress? job = jobTable.firstWhereOrNull((e) => e.id == data.id);
CmFileLog? job = jobTable.firstWhereOrNull((e) => e.id == data.id);
if (job == null) {
job = JobProgress();
job = CmFileLog();
jobTable.add(job);
final currentSelectedTab =
gFFI.serverModel.tabController.state.value.selectedTabInfo;
@@ -68,14 +78,14 @@ class CmFileModel {
}
}
job.id = data.id;
job.isRemoteToLocal = data.isRemote;
job.action =
data.isRemote ? CmFileAction.remoteToLocal : CmFileAction.localToRemote;
job.fileName = data.path;
job.totalSize = data.totalSize;
job.finishedSize = data.finishedSize;
if (job.finishedSize > data.totalSize) {
job.finishedSize = data.totalSize;
}
job.isRemoteToLocal = data.isRemote;
if (job.finishedSize > 0) {
if (job.finishedSize < job.totalSize) {
@@ -99,6 +109,119 @@ class CmFileModel {
}
jobTable.refresh();
}
_onFileRemove(dynamic log) {
try {
dynamic d = jsonDecode(log);
FileActionLog data = FileActionLog.fromJson(d);
Client? client =
gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == data.connId);
var jobTable = _jobTables[data.connId];
if (jobTable == null) {
debugPrint("jobTable should not be null");
return;
}
int removeUnreadCount = 0;
if (data.dir) {
bool isChild(String parent, String child) {
if (child.startsWith(parent) && child.length > parent.length) {
final suffix = child.substring(parent.length);
return suffix.startsWith('/') || suffix.startsWith('\\');
}
return false;
}
removeUnreadCount = jobTable
.where((e) =>
e.action == CmFileAction.remove &&
isChild(data.path, e.fileName))
.length;
jobTable.removeWhere((e) =>
e.action == CmFileAction.remove && isChild(data.path, e.fileName));
}
jobTable.add(CmFileLog()
..id = data.id
..fileName = data.path
..action = CmFileAction.remove
..state = JobState.done);
final currentSelectedTab =
gFFI.serverModel.tabController.state.value.selectedTabInfo;
if (!(gFFI.chatModel.isShowCMSidePage &&
currentSelectedTab.key == data.connId.toString())) {
// Wrong number if unreadCount changes during deletion, which rarely happens
RxInt? rx = client?.unreadChatMessageCount;
if (rx != null) {
if (rx.value >= removeUnreadCount) {
rx.value -= removeUnreadCount;
}
rx.value += 1;
}
}
jobTable.refresh();
} catch (e) {
debugPrint('$e');
}
}
_onDirCreate(dynamic log) {
try {
dynamic d = jsonDecode(log);
FileActionLog data = FileActionLog.fromJson(d);
Client? client =
gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == data.connId);
var jobTable = _jobTables[data.connId];
if (jobTable == null) {
debugPrint("jobTable should not be null");
return;
}
jobTable.add(CmFileLog()
..id = data.id
..fileName = data.path
..action = CmFileAction.createDir
..state = JobState.done);
final currentSelectedTab =
gFFI.serverModel.tabController.state.value.selectedTabInfo;
if (!(gFFI.chatModel.isShowCMSidePage &&
currentSelectedTab.key == data.connId.toString())) {
client?.unreadChatMessageCount.value += 1;
}
jobTable.refresh();
} catch (e) {
debugPrint('$e');
}
}
}
enum CmFileAction {
none,
remoteToLocal,
localToRemote,
remove,
createDir,
}
class CmFileLog {
JobState state = JobState.none;
var id = 0;
var speed = 0.0;
var finishedSize = 0;
var totalSize = 0;
CmFileAction action = CmFileAction.none;
var fileName = "";
var err = "";
int lastTransferredSize = 0;
String display() {
if (state == JobState.done && err == "skipped") {
return translate("Skipped");
}
return state.display();
}
bool isTransfer() {
return action == CmFileAction.remoteToLocal ||
action == CmFileAction.localToRemote;
}
}
class TransferJobSerdeData {
@@ -140,3 +263,25 @@ class TransferJobSerdeData {
error: d['error'] ?? '',
);
}
class FileActionLog {
int id = 0;
int connId = 0;
String path = '';
bool dir = false;
FileActionLog({
required this.connId,
required this.id,
required this.path,
required this.dir,
});
FileActionLog.fromJson(dynamic d)
: this(
connId: d['connId'] ?? 0,
id: d['id'] ?? 0,
path: d['path'] ?? '',
dir: d['dir'] ?? false,
);
}

View File

@@ -1006,7 +1006,7 @@ extension JobStateDisplay on JobState {
case JobState.none:
return translate("Waiting");
case JobState.inProgress:
return translate("Transfer File");
return translate("Transfer file");
case JobState.done:
return translate("Finished");
case JobState.error:

View File

@@ -138,8 +138,9 @@ class FfiModel with ChangeNotifier {
sessionId = parent.target!.sessionId;
}
Rect? displaysRect() {
final displays = _pi.getCurDisplays();
Rect? globalDisplaysRect() => _getDisplaysRect(_pi.displays);
Rect? displaysRect() => _getDisplaysRect(_pi.getCurDisplays());
Rect? _getDisplaysRect(List<Display> displays) {
if (displays.isEmpty) {
return null;
}
@@ -352,7 +353,7 @@ class FfiModel with ChangeNotifier {
}
} else if (name == "cm_file_transfer_log") {
if (isDesktop) {
gFFI.cmFileModel.onFileTransferLog(evt['log']);
gFFI.cmFileModel.onFileTransferLog(evt);
}
} else {
debugPrint('Unknown event name: $name');
@@ -430,15 +431,19 @@ class FfiModel with ChangeNotifier {
handleSwitchDisplay(
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
final curDisplay = int.parse(evt['display']);
final display = int.parse(evt['display']);
if (_pi.currentDisplay != kAllDisplayValue) {
if (bind.peerGetDefaultSessionsCount(id: peerId) > 1) {
if (curDisplay != _pi.currentDisplay) {
if (display != _pi.currentDisplay) {
return;
}
}
_pi.currentDisplay = curDisplay;
if (!_pi.isSupportMultiUiSession) {
_pi.currentDisplay = display;
}
// If `isSupportMultiUiSession` is true, the switch display message should not be used to update current display.
// It is only used to update the display info.
}
var newDisplay = Display();
@@ -451,16 +456,24 @@ class FfiModel with ChangeNotifier {
int.tryParse(evt['original_width']) ?? kInvalidResolutionValue;
newDisplay.originalHeight =
int.tryParse(evt['original_height']) ?? kInvalidResolutionValue;
_pi.displays[curDisplay] = newDisplay;
_pi.displays[display] = newDisplay;
updateCurDisplay(sessionId);
try {
CurrentDisplayState.find(peerId).value = curDisplay;
} catch (e) {
//
if (!_pi.isSupportMultiUiSession || _pi.currentDisplay == display) {
updateCurDisplay(sessionId);
}
if (!_pi.isSupportMultiUiSession) {
try {
CurrentDisplayState.find(peerId).value = display;
} catch (e) {
//
}
}
parent.target?.recordingModel.onSwitchDisplay();
handleResolutions(peerId, evt['resolutions']);
if (!_pi.isSupportMultiUiSession || _pi.currentDisplay == display) {
handleResolutions(peerId, evt['resolutions']);
}
notifyListeners();
}
@@ -627,7 +640,9 @@ class FfiModel with ChangeNotifier {
/// Handle the peer info event based on [evt].
handlePeerInfo(Map<String, dynamic> evt, String peerId, bool isCache) async {
cachedPeerData.peerInfo = evt;
// Map clone is required here, otherwise "evt" may be changed by other threads through the reference.
// Because this function is asynchronous, there's an "await" in this function.
cachedPeerData.peerInfo = {...evt};
// recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
bind.mainLoadRecentPeers();
@@ -666,11 +681,12 @@ class FfiModel with ChangeNotifier {
if (connType == ConnType.fileTransfer) {
parent.target?.fileModel.onReady();
} else if (connType == ConnType.defaultConn) {
_pi.displays = [];
List<Display> newDisplays = [];
List<dynamic> displays = json.decode(evt['displays']);
for (int i = 0; i < displays.length; ++i) {
_pi.displays.add(evtToDisplay(displays[i]));
newDisplays.add(evtToDisplay(displays[i]));
}
_pi.displays.value = newDisplays;
_pi.displaysCount.value = _pi.displays.length;
if (_pi.currentDisplay < _pi.displays.length) {
// now replaced to _updateCurDisplay
@@ -860,7 +876,7 @@ class FfiModel with ChangeNotifier {
for (int i = 0; i < displays.length; ++i) {
newDisplays.add(evtToDisplay(displays[i]));
}
_pi.displays = newDisplays;
_pi.displays.value = newDisplays;
_pi.displaysCount.value = _pi.displays.length;
if (_pi.currentDisplay == kAllDisplayValue) {
@@ -908,11 +924,11 @@ class FfiModel with ChangeNotifier {
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
} else {
try {
final updateJson = json.decode(updateData);
final updateJson = json.decode(updateData) as Map<String, dynamic>;
for (final key in updateJson.keys) {
_pi.platformAdditions[key] = updateJson[key];
}
if (!updateJson.contains(kPlatformAdditionsVirtualDisplays)) {
if (!updateJson.containsKey(kPlatformAdditionsVirtualDisplays)) {
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
}
} catch (e) {
@@ -1845,6 +1861,7 @@ class QualityMonitorData {
String? delay;
String? targetBitrate;
String? codecFormat;
String? chroma;
}
class QualityMonitorModel with ChangeNotifier {
@@ -1898,6 +1915,9 @@ class QualityMonitorModel with ChangeNotifier {
if ((evt['codec_format'] as String).isNotEmpty) {
_data.codecFormat = evt['codec_format'];
}
if ((evt['chroma'] as String).isNotEmpty) {
_data.chroma = evt['chroma'];
}
notifyListeners();
} catch (e) {
//
@@ -2321,7 +2341,7 @@ class PeerInfo with ChangeNotifier {
bool isSupportMultiUiSession = false;
int currentDisplay = 0;
int primaryDisplay = kInvalidDisplayIndex;
List<Display> displays = [];
RxList<Display> displays = <Display>[].obs;
Features features = Features();
List<Resolution> resolutions = [];
Map<String, dynamic> platformAdditions = {};

View File

@@ -15,7 +15,7 @@ import 'package:path_provider/path_provider.dart';
import '../common.dart';
import '../generated_bridge.dart';
class RgbaFrame extends Struct {
final class RgbaFrame extends Struct {
@Uint32()
external int len;
external Pointer<Uint8> data;

View File

@@ -22,10 +22,10 @@ class PeerTabModel with ChangeNotifier {
int get currentTab => _currentTab;
int _currentTab = 0; // index in tabNames
List<String> tabNames = [
'Recent Sessions',
'Recent sessions',
'Favorites',
'Discovered',
'Address Book',
'Address book',
'Group',
];
final List<IconData> icons = [

View File

@@ -8,7 +8,7 @@ import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:window_manager/window_manager.dart';
import '../common.dart';
@@ -380,7 +380,7 @@ class ServerModel with ChangeNotifier {
await bind.mainStartService();
updateClientState();
if (Platform.isAndroid) {
Wakelock.enable();
WakelockPlus.enable();
}
}
@@ -393,7 +393,7 @@ class ServerModel with ChangeNotifier {
notifyListeners();
if (!Platform.isLinux) {
// current linux is not supported
Wakelock.disable();
WakelockPlus.disable();
}
}
@@ -690,6 +690,7 @@ class Client {
bool file = false;
bool restart = false;
bool recording = false;
bool blockInput = false;
bool disconnected = false;
bool fromSwitch = false;
bool inVoiceCall = false;
@@ -713,6 +714,7 @@ class Client {
file = json['file'];
restart = json['restart'];
recording = json['recording'];
blockInput = json['block_input'];
disconnected = json['disconnected'];
fromSwitch = json['from_switch'];
inVoiceCall = json['in_voice_call'];
@@ -733,6 +735,7 @@ class Client {
data['file'] = file;
data['restart'] = restart;
data['recording'] = recording;
data['block_input'] = blockInput;
data['disconnected'] = disconnected;
data['from_switch'] = fromSwitch;
return data;

View File

@@ -1,4 +1,4 @@
platform :osx, '10.12'
platform :osx, '10.14'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@@ -3,7 +3,9 @@ PODS:
- FlutterMacOS
- desktop_multi_window (0.0.1):
- FlutterMacOS
- device_info_plus_macos (0.0.1):
- device_info_plus (0.0.1):
- FlutterMacOS
- file_selector_macos (0.0.1):
- FlutterMacOS
- flutter_custom_cursor (0.0.1):
- FlutterMacOS
@@ -27,7 +29,10 @@ PODS:
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- wakelock_macos (0.0.1):
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- wakelock_plus (0.0.1):
- FlutterMacOS
- window_manager (0.2.0):
- FlutterMacOS
@@ -37,7 +42,8 @@ PODS:
DEPENDENCIES:
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
- desktop_multi_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos`)
- device_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus_macos/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- flutter_custom_cursor (from `Flutter/ephemeral/.symlinks/plugins/flutter_custom_cursor/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
@@ -47,7 +53,8 @@ DEPENDENCIES:
- texture_rgba_renderer (from `Flutter/ephemeral/.symlinks/plugins/texture_rgba_renderer/macos`)
- uni_links_desktop (from `Flutter/ephemeral/.symlinks/plugins/uni_links_desktop/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`)
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
- window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`)
@@ -60,8 +67,10 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos
desktop_multi_window:
:path: Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos
device_info_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus_macos/macos
device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
file_selector_macos:
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
flutter_custom_cursor:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_custom_cursor/macos
FlutterMacOS:
@@ -80,8 +89,10 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/uni_links_desktop/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
wakelock_macos:
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos
video_player_avfoundation:
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin
wakelock_plus:
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
window_manager:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
window_size:
@@ -90,21 +101,23 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
desktop_multi_window: 566489c048b501134f9d7fb6a2354c60a9126486
device_info_plus_macos: 1ad388a1ef433505c4038e7dd9605aadd1e2e9c7
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
flutter_custom_cursor: 629957115075c672287bd0fa979d863ccf6024f7
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
texture_rgba_renderer: cbed959a3c127122194a364e14b8577bd62dc8f2
uni_links_desktop: 45900fb319df48fcdea2df0756e9c2626696b026
url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
video_player_avfoundation: 8563f13d8fc8b2c29dc2d09e60b660e4e8128837
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
PODFILE CHECKSUM: c7161fcf45d4fd9025dc0f48a76d6e64e52f8176
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
COCOAPODS: 1.12.1

View File

@@ -210,7 +210,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
33CC10EC2044A3C60003C045 = {

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -5,7 +5,7 @@ import desktop_multi_window
// import bitsdojo_window_macos
import desktop_drop
import device_info_plus_macos
import device_info_plus
import flutter_custom_cursor
import package_info_plus
import path_provider_foundation
@@ -14,7 +14,7 @@ import sqflite
// import tray_manager
import uni_links_desktop
import url_launcher_macos
import wakelock_macos
import wakelock_plus
import window_manager
import window_size
import texture_rgba_renderer
@@ -35,17 +35,18 @@ class MainFlutterWindow: NSWindow {
FlutterMultiWindowPlugin.setOnWindowCreatedCallback { controller in
// Register the plugin which you want access from other isolate.
// DesktopLifecyclePlugin.register(with: controller.registrar(forPlugin: "DesktopLifecyclePlugin"))
// Note: copy below from above RegisterGeneratedPlugins
self.setMethodHandler(registrar: controller.registrar(forPlugin: "RustDeskPlugin"))
DesktopDropPlugin.register(with: controller.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: controller.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FlutterCustomCursorPlugin.register(with: controller.registrar(forPlugin: "FlutterCustomCursorPlugin"))
FLTPackageInfoPlusPlugin.register(with: controller.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
FPPPackageInfoPlusPlugin.register(with: controller.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: controller.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: controller.registrar(forPlugin: "SqflitePlugin"))
// TrayManagerPlugin.register(with: controller.registrar(forPlugin: "TrayManagerPlugin"))
UniLinksDesktopPlugin.register(with: controller.registrar(forPlugin: "UniLinksDesktopPlugin"))
UrlLauncherPlugin.register(with: controller.registrar(forPlugin: "UrlLauncherPlugin"))
WakelockMacosPlugin.register(with: controller.registrar(forPlugin: "WakelockMacosPlugin"))
WakelockPlusMacosPlugin.register(with: controller.registrar(forPlugin: "WakelockPlusMacosPlugin"))
WindowSizePlugin.register(with: controller.registrar(forPlugin: "WindowSizePlugin"))
TextureRgbaRendererPlugin.register(with: controller.registrar(forPlugin: "TextureRgbaRendererPlugin"))
}

View File

@@ -1,135 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 53;
objects = {
/* Begin PBXFileReference section */
ADDEDBA66A6E1 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXGroup section */
ADDEDBA66A6E2 /* Required for static linking */ = {
isa = PBXGroup;
children = (
ADDEDBA66A6E1 /* libresolv.tbd */,
);
name = "Required for static linking";
sourceTree = "<group>";
};
CA603C4309E122869D176AE5 /* Products */ = {
isa = PBXGroup;
children = (
);
name = Products;
sourceTree = "<group>";
};
CA603C4309E198AF0B5890DB /* Frameworks */ = {
isa = PBXGroup;
children = (
ADDEDBA66A6E2 /* Required for static linking */,
);
name = Frameworks;
sourceTree = "<group>";
};
CA603C4309E1D65BC3C892A8 = {
isa = PBXGroup;
children = (
CA603C4309E122869D176AE5 /* Products */,
CA603C4309E198AF0B5890DB /* Frameworks */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXProject section */
CA603C4309E1E04653AD465F /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
};
buildConfigurationList = CA603C4309E180E02D6C7F57 /* Build configuration list for PBXProject "rustdesk" */;
compatibilityVersion = "Xcode 11.4";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = CA603C4309E1D65BC3C892A8;
productRefGroup = CA603C4309E122869D176AE5 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
);
};
/* End PBXProject section */
/* Begin XCBuildConfiguration section */
CA608F3F78EE228BE02872F8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
CARGO_XCODE_BUILD_MODE = debug;
CARGO_XCODE_FEATURES = "";
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = RustDesk;
SDKROOT = macosx;
SUPPORTS_MACCATALYST = YES;
};
name = Debug;
};
CA608F3F78EE3CC16B37690B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
CARGO_XCODE_BUILD_MODE = release;
CARGO_XCODE_FEATURES = "";
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
PRODUCT_NAME = RustDesk;
SDKROOT = macosx;
SUPPORTS_MACCATALYST = YES;
OTHER_LDFLAGS = (
"-sectcreate",
__CGPreLoginApp,
__cgpreloginapp,
/dev/null,
);
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
CA603C4309E180E02D6C7F57 /* Build configuration list for PBXProject "rustdesk" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CA608F3F78EE3CC16B37690B /* Release */,
CA608F3F78EE228BE02872F8 /* Debug */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = CA603C4309E1E04653AD465F /* Project object */;
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.2.4+39
environment:
sdk: ">=2.17.0"
sdk: '^3.1.0'
dependencies:
flutter:
@@ -27,31 +27,29 @@ dependencies:
flutter_localizations:
sdk: flutter
ffi: ^2.0.1
path_provider: ^2.0.12
external_path: ^1.0.1
provider: ^6.0.3
ffi: ^2.1.0
path_provider: ^2.1.1
external_path: ^1.0.3
provider: ^6.0.5
tuple: ^2.0.0
wakelock: ^0.6.2
# Keep this version for the compatibility of some old systems like win7.
device_info_plus: ^4.1.2
wakelock_plus: ^1.1.3
#firebase_analytics: ^9.1.5
package_info_plus: ^3.1.2
url_launcher: ^6.0.9
package_info_plus: ^4.2.0
url_launcher: ^6.2.1
toggle_switch: ^2.1.0
dash_chat_2:
dash_chat_2:
git:
url: https://github.com/rustdesk-org/Dash-Chat-2
draggable_float_widget: ^0.0.2
draggable_float_widget: ^0.1.0
settings_ui: ^2.0.2
flutter_breadcrumb: ^1.0.1
http: ^0.13.4
http: ^1.1.0
qr_code_scanner: ^1.0.0
zxing2: ^0.2.0
image_picker: ^0.8.5
image: ^4.0.17
back_button_interceptor: ^6.0.1
flutter_rust_bridge: "<1.76.0"
flutter_rust_bridge: "1.80.1"
window_manager:
git:
url: https://github.com/rustdesk-org/window_manager
@@ -68,10 +66,7 @@ dependencies:
get: ^4.6.5
visibility_detector: ^0.4.0+2
contextmenu: ^3.0.0
desktop_drop:
git:
url: https://github.com/rustdesk-org/flutter-plugins
path: ./packages/desktop_drop
desktop_drop: ^0.4.4
scroll_pos: ^0.4.0
debounce_throttle: ^2.0.0
file_picker: ^5.1.0
@@ -84,7 +79,7 @@ dependencies:
git:
url: https://github.com/rustdesk-org/flutter_improved_scrolling
uni_links: ^0.5.1
uni_links_desktop:
uni_links_desktop:
git:
url: https://github.com/rustdesk-org/uni_links_desktop
path: ^1.8.1
@@ -105,6 +100,7 @@ dependencies:
url: https://github.com/21pages/dynamic_layouts.git
ref: 24cb88413fa5181d949ddacbb30a65d5c459e7d9
pull_down_button: ^0.9.3
device_info_plus: ^9.1.0
dev_dependencies:
icons_launcher: ^2.0.4
@@ -113,7 +109,7 @@ dev_dependencies:
build_runner: ^2.4.6
freezed: ^2.4.2
flutter_lints: ^2.0.2
ffigen: ^7.2.4
ffigen: ^8.0.2
# rerun: flutter pub run flutter_launcher_icons
flutter_icons:

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
cargo install flutter_rust_bridge_codegen --version 1.75.3 --features uuid
cargo install flutter_rust_bridge_codegen --version 1.80.1 --features uuid
flutter pub get
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ../src/flutter_ffi.rs --dart-output ./lib/generated_bridge.dart --c-output ./macos/Runner/bridge_generated.h
# call `flutter clean` if cargo build fails