mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-05 15:11:27 +03:00
Merge remote-tracking branch 'upstream/master'
# Conflicts: # Cargo.lock # src/server/connection.rs
This commit is contained in:
@@ -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") } }
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
ext.kotlin_version = '1.9.10'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ?? '-'),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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: () {
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'),
|
||||
],
|
||||
),
|
||||
]),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user