Merge branch 'ios_1.2.2'

This commit is contained in:
rustdesk 2023-09-03 00:16:08 +08:00
commit 5b802e9edd
28 changed files with 943 additions and 667 deletions

View File

@ -428,6 +428,13 @@ jobs:
prefix-key: rustdesk-lib-cache prefix-key: rustdesk-lib-cache
key: ${{ matrix.job.target }}-${{ matrix.job.extra-build-features }} key: ${{ matrix.job.target }}-${{ matrix.job.extra-build-features }}
- name: Install flutter rust bridge deps
shell: bash
run: |
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
pushd flutter && flutter pub get && popd
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/ios/Runner/bridge_generated.h
- name: Build rustdesk lib - name: Build rustdesk lib
env: env:
VCPKG_ROOT: /opt/rustdesk_thirdparty_lib/vcpkg VCPKG_ROOT: /opt/rustdesk_thirdparty_lib/vcpkg
@ -439,7 +446,9 @@ jobs:
shell: bash shell: bash
run: | run: |
pushd flutter pushd flutter
flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info --no-codesign # flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info --no-codesign
# for easy debugging
flutter build ipa --release --no-codesign
# - name: Upload Artifacts # - name: Upload Artifacts
# # if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true' # # if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true'

View File

@ -1,2 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info # https://docs.flutter.dev/deployment/ios
# flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info
# no obfuscate, because no easy to check errors
flutter build ipa --release

View File

@ -75,7 +75,7 @@ DEPENDENCIES:
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`) - qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`)
@ -106,7 +106,7 @@ EXTERNAL SOURCES:
package_info_plus: package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation: path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/ios" :path: ".symlinks/plugins/path_provider_foundation/darwin"
qr_code_scanner: qr_code_scanner:
:path: ".symlinks/plugins/qr_code_scanner/ios" :path: ".symlinks/plugins/qr_code_scanner/ios"
sqflite: sqflite:
@ -141,6 +141,6 @@ SPEC CHECKSUMS:
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
PODFILE CHECKSUM: c649b4e69a3086d323110011d04604e416ad0dcd PODFILE CHECKSUM: 2aff76ba0ac13439479560d1d03e9b4479f5c9e1
COCOAPODS: 1.12.0 COCOAPODS: 1.12.1

View File

@ -208,6 +208,7 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
); );
name = "Thin Binary"; name = "Thin Binary";
outputPaths = ( outputPaths = (
@ -437,6 +438,7 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb; PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRIP_STYLE = "non-global";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
@ -634,6 +636,7 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb; PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRIP_STYLE = "non-global";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -723,6 +726,7 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb; PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRIP_STYLE = "non-global";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";

View File

@ -13,9 +13,7 @@ import Flutter
} }
public func dummyMethodToEnforceBundling() { public func dummyMethodToEnforceBundling() {
get_rgba(); dummy_method_to_enforce_bundling();
// free_rgba(nil); session_get_rgba(nil);
// get_by_name("", "");
// set_by_name("", "");
} }
} }

View File

@ -1,122 +1,122 @@
{ {
"images": [ "images" : [
{ {
"filename": "Icon-App-20x20@2x.png", "filename" : "Icon-App-20x20@2x.png",
"idiom": "iphone", "idiom" : "iphone",
"scale": "2x", "scale" : "2x",
"size": "20x20" "size" : "20x20"
}, },
{ {
"filename": "Icon-App-20x20@3x.png", "filename" : "Icon-App-20x20@3x.png",
"idiom": "iphone", "idiom" : "iphone",
"scale": "3x", "scale" : "3x",
"size": "20x20" "size" : "20x20"
}, },
{ {
"filename": "Icon-App-29x29@1x.png", "filename" : "Icon-App-29x29@1x.png",
"idiom": "iphone", "idiom" : "iphone",
"scale": "1x", "scale" : "1x",
"size": "29x29" "size" : "29x29"
}, },
{ {
"filename": "Icon-App-29x29@2x.png", "filename" : "Icon-App-29x29@2x.png",
"idiom": "iphone", "idiom" : "iphone",
"scale": "2x", "scale" : "2x",
"size": "29x29" "size" : "29x29"
}, },
{ {
"filename": "Icon-App-29x29@3x.png", "filename" : "Icon-App-29x29@3x.png",
"idiom": "iphone", "idiom" : "iphone",
"scale": "3x", "scale" : "3x",
"size": "29x29" "size" : "29x29"
}, },
{ {
"filename": "Icon-App-40x40@2x.png", "filename" : "Icon-App-40x40@2x.png",
"idiom": "iphone", "idiom" : "iphone",
"scale": "2x", "scale" : "2x",
"size": "40x40" "size" : "40x40"
}, },
{ {
"filename": "Icon-App-40x40@3x.png", "filename" : "Icon-App-40x40@3x.png",
"idiom": "iphone", "idiom" : "iphone",
"scale": "3x", "scale" : "3x",
"size": "40x40" "size" : "40x40"
}, },
{ {
"filename": "Icon-App-60x60@2x.png", "filename" : "Icon-App-60x60@2x.png",
"idiom": "iphone", "idiom" : "iphone",
"scale": "2x", "scale" : "2x",
"size": "60x60" "size" : "60x60"
}, },
{ {
"filename": "Icon-App-60x60@3x.png", "filename" : "Icon-App-60x60@3x.png",
"idiom": "iphone", "idiom" : "iphone",
"scale": "3x", "scale" : "3x",
"size": "60x60" "size" : "60x60"
}, },
{ {
"filename": "Icon-App-20x20@1x.png", "filename" : "Icon-App-20x20@1x.png",
"idiom": "ipad", "idiom" : "ipad",
"scale": "1x", "scale" : "1x",
"size": "20x20" "size" : "20x20"
}, },
{ {
"filename": "Icon-App-20x20@2x.png", "filename" : "Icon-App-20x20@2x.png",
"idiom": "ipad", "idiom" : "ipad",
"scale": "2x", "scale" : "2x",
"size": "20x20" "size" : "20x20"
}, },
{ {
"filename": "Icon-App-29x29@1x.png", "filename" : "Icon-App-29x29@1x.png",
"idiom": "ipad", "idiom" : "ipad",
"scale": "1x", "scale" : "1x",
"size": "29x29" "size" : "29x29"
}, },
{ {
"filename": "Icon-App-29x29@2x.png", "filename" : "Icon-App-29x29@2x.png",
"idiom": "ipad", "idiom" : "ipad",
"scale": "2x", "scale" : "2x",
"size": "29x29" "size" : "29x29"
}, },
{ {
"filename": "Icon-App-40x40@1x.png", "filename" : "Icon-App-40x40@1x.png",
"idiom": "ipad", "idiom" : "ipad",
"scale": "1x", "scale" : "1x",
"size": "40x40" "size" : "40x40"
}, },
{ {
"filename": "Icon-App-40x40@2x.png", "filename" : "Icon-App-40x40@2x.png",
"idiom": "ipad", "idiom" : "ipad",
"scale": "2x", "scale" : "2x",
"size": "40x40" "size" : "40x40"
}, },
{ {
"filename": "Icon-App-76x76@1x.png", "filename" : "Icon-App-76x76@1x.png",
"idiom": "ipad", "idiom" : "ipad",
"scale": "1x", "scale" : "1x",
"size": "76x76" "size" : "76x76"
}, },
{ {
"filename": "Icon-App-76x76@2x.png", "filename" : "Icon-App-76x76@2x.png",
"idiom": "ipad", "idiom" : "ipad",
"scale": "2x", "scale" : "2x",
"size": "76x76" "size" : "76x76"
}, },
{ {
"filename": "Icon-App-83.5x83.5@2x.png", "filename" : "Icon-App-83.5x83.5@2x.png",
"idiom": "ipad", "idiom" : "ipad",
"scale": "2x", "scale" : "2x",
"size": "83.5x83.5" "size" : "83.5x83.5"
}, },
{ {
"filename": "Icon-App-1024x1024@1x.png", "filename" : "Icon-App-1024x1024@1x.png",
"idiom": "ios-marketing", "idiom" : "ios-marketing",
"scale": "1x", "scale" : "1x",
"size": "1024x1024" "size" : "1024x1024"
} }
], ],
"info": { "info" : {
"author": "icons_launcher", "author" : "xcode",
"version": 1 "version" : 1
} }
} }

View File

@ -1,23 +1,23 @@
{ {
"images" : [ "images" : [
{ {
"idiom" : "universal",
"filename" : "LaunchImage.png", "filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "universal",
"filename" : "LaunchImage@2x.png", "filename" : "LaunchImage@2x.png",
"idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "universal",
"filename" : "LaunchImage@3x.png", "filename" : "LaunchImage@3x.png",
"idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }
], ],
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
} }
} }

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Flutter View Controller--> <!--Flutter View Controller-->
@ -14,13 +16,14 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="48" y="-2"/>
</scene> </scene>
</scenes> </scenes>
</document> </document>

View File

@ -1,3 +1,3 @@
#import "GeneratedPluginRegistrant.h" #import "GeneratedPluginRegistrant.h"
#import "ffi.h" #import "bridge_generated.h"

View File

@ -1,4 +0,0 @@
void* get_rgba();
void free_rgba(void*);
void set_by_name(const char*, const char*);
const char* get_by_name(const char*, const char*);

View File

@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'package:debounce_throttle/debounce_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../common.dart'; import '../../common.dart';
@ -1225,76 +1225,9 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
final quality = await bind.sessionGetCustomImageQuality(sessionId: sessionId); final quality = await bind.sessionGetCustomImageQuality(sessionId: sessionId);
qualityInitValue = qualityInitValue =
quality != null && quality.isNotEmpty ? quality[0].toDouble() : 50.0; quality != null && quality.isNotEmpty ? quality[0].toDouble() : 50.0;
const qualityMinValue = 10.0; if (qualityInitValue < 10 || qualityInitValue > 2000) {
const qualityMoreThresholdValue = 100.0; qualityInitValue = 50;
const qualityMaxValue = 2000.0;
if (qualityInitValue < qualityMinValue) {
qualityInitValue = qualityMinValue;
} }
if (qualityInitValue > qualityMaxValue) {
qualityInitValue = qualityMaxValue;
}
final RxDouble qualitySliderValue = RxDouble(qualityInitValue);
final moreQualityInitValue = qualityInitValue > qualityMoreThresholdValue;
final RxBool moreQualityChecked = RxBool(moreQualityInitValue);
final debouncerQuality = Debouncer<double>(
Duration(milliseconds: 1000),
onChanged: (double v) {
setCustomValues(quality: v);
},
initialValue: qualityInitValue,
);
final qualitySlider = Obx(() => Row(
children: [
Expanded(
flex: 3,
child: Slider(
value: qualitySliderValue.value,
min: qualityMinValue,
max: moreQualityChecked.value
? qualityMaxValue
: qualityMoreThresholdValue,
divisions: 18,
onChanged: (double value) {
qualitySliderValue.value = value;
debouncerQuality.value = value;
},
)),
Expanded(
flex: 1,
child: Text(
'${qualitySliderValue.value.round()}%',
style: const TextStyle(fontSize: 15),
)),
Expanded(
flex: 1,
child: Text(
translate('Bitrate'),
style: const TextStyle(fontSize: 15),
)),
Expanded(
flex: 1,
child: Row(
children: [
Checkbox(
value: moreQualityChecked.value,
onChanged: (bool? value) {
moreQualityChecked.value = value!;
if (!value &&
qualitySliderValue.value >
qualityMoreThresholdValue) {
qualitySliderValue.value = qualityMoreThresholdValue;
debouncerQuality.value = qualityMoreThresholdValue;
}
},
).marginOnly(right: 5),
Expanded(
child: Text(translate('More')),
)
],
)),
],
));
// fps // fps
final fpsOption = final fpsOption =
await bind.sessionGetOption(sessionId: sessionId, arg: 'custom-fps'); await bind.sessionGetOption(sessionId: sessionId, arg: 'custom-fps');
@ -1302,55 +1235,20 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
if (fpsInitValue < 5 || fpsInitValue > 120) { if (fpsInitValue < 5 || fpsInitValue > 120) {
fpsInitValue = 30; fpsInitValue = 30;
} }
final RxDouble fpsSliderValue = RxDouble(fpsInitValue);
final debouncerFps = Debouncer<double>(
Duration(milliseconds: 1000),
onChanged: (double v) {
setCustomValues(fps: v);
},
initialValue: qualityInitValue,
);
bool? direct; bool? direct;
try { try {
direct = direct =
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect; ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
} catch (_) {} } catch (_) {}
final fpsSlider = Offstage( bool notShowFps = (await bind.mainIsUsingPublicServer() && direct != true) ||
offstage: (await bind.mainIsUsingPublicServer() && direct != true) || version_cmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
version_cmp(ffi.ffiModel.pi.version, '1.2.0') < 0,
child: Row(
children: [
Expanded(
flex: 3,
child: Obx((() => Slider(
value: fpsSliderValue.value,
min: 5,
max: 120,
divisions: 23,
onChanged: (double value) {
fpsSliderValue.value = value;
debouncerFps.value = value;
},
)))),
Expanded(
flex: 1,
child: Obx(() => Text(
'${fpsSliderValue.value.round()}',
style: const TextStyle(fontSize: 15),
))),
Expanded(
flex: 2,
child: Text(
translate('FPS'),
style: const TextStyle(fontSize: 15),
))
],
),
);
final content = Column( final content = customImageQualityWidget(
children: [qualitySlider, fpsSlider], initQuality: qualityInitValue,
); initFps: fpsInitValue,
setQuality: (v) => setCustomValues(quality: v),
setFps: (v) => setCustomValues(fps: v),
showFps: !notShowFps);
msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]); msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]);
} }

View File

@ -26,15 +26,31 @@ class DraggableChatWindow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Draggable( 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, checkKeyboard: true,
position: position, position: position,
width: width, width: width,
height: height, height: height,
builder: (context, onPanUpdate) { builder: (context, onPanUpdate) {
final child = isIOS final child =
? ChatPage(chatModel: chatModel) Scaffold(
: Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
appBar: CustomAppBar( appBar: CustomAppBar(
onPanUpdate: onPanUpdate, onPanUpdate: onPanUpdate,
@ -331,6 +347,69 @@ class _DraggableState extends State<Draggable> {
} }
} }
class IOSDraggable extends StatefulWidget {
const IOSDraggable({
Key? key,
this.position = Offset.zero,
required this.chatModel,
required this.width,
required this.height,
required this.builder})
: super(key: key);
final Offset position;
final ChatModel chatModel;
final double width;
final double height;
final Widget Function(BuildContext) builder;
@override
_IOSDraggableState createState() => _IOSDraggableState();
}
class _IOSDraggableState extends State<IOSDraggable> {
late Offset _position;
late ChatModel _chatModel;
late double _width;
late double _height;
@override
void initState() {
super.initState();
_position = widget.position;
_chatModel = widget.chatModel;
_width = widget.width;
_height = widget.height;
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
left: _position.dx,
top: _position.dy,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
_position += details.delta;
});
},
child: Material(
child:
Container(
width: _width,
height: _height,
child: widget.builder(context),
),
),
),
),
],
);
}
}
class QualityMonitor extends StatelessWidget { class QualityMonitor extends StatelessWidget {
final QualityMonitorModel qualityMonitorModel; final QualityMonitorModel qualityMonitorModel;
QualityMonitor(this.qualityMonitorModel); QualityMonitor(this.qualityMonitorModel);

View File

@ -0,0 +1,277 @@
import 'package:debounce_throttle/debounce_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
customImageQualityWidget(
{required double initQuality,
required double initFps,
required Function(double) setQuality,
required Function(double) setFps,
required bool showFps}) {
final qualityValue = initQuality.obs;
final fpsValue = initFps.obs;
final RxBool moreQualityChecked = RxBool(qualityValue.value > 100);
final debouncerQuality = Debouncer<double>(
Duration(milliseconds: 1000),
onChanged: (double v) {
setQuality(v);
},
initialValue: qualityValue.value,
);
final debouncerFps = Debouncer<double>(
Duration(milliseconds: 1000),
onChanged: (double v) {
setFps(v);
},
initialValue: fpsValue.value,
);
onMoreChanged(bool? value) {
if (value == null) return;
moreQualityChecked.value = value;
if (!value && qualityValue.value > 100) {
qualityValue.value = 100;
}
debouncerQuality.value = qualityValue.value;
}
return Column(
children: [
Obx(() => Row(
children: [
Expanded(
flex: 3,
child: Slider(
value: qualityValue.value,
min: 10.0,
max: moreQualityChecked.value ? 2000 : 100,
divisions: moreQualityChecked.value ? 199 : 18,
onChanged: (double value) async {
qualityValue.value = value;
debouncerQuality.value = value;
},
),
),
Expanded(
flex: 1,
child: Text(
'${qualityValue.value.round()}%',
style: const TextStyle(fontSize: 15),
)),
Expanded(
flex: isMobile ? 2 : 1,
child: Text(
translate('Bitrate'),
style: const TextStyle(fontSize: 15),
)),
// mobile doesn't have enough space
if (!isMobile)
Expanded(
flex: 1,
child: Row(
children: [
Checkbox(
value: moreQualityChecked.value,
onChanged: onMoreChanged,
),
Expanded(
child: Text(translate('More')),
)
],
))
],
)),
if (isMobile)
Obx(() => Row(
children: [
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Checkbox(
value: moreQualityChecked.value,
onChanged: onMoreChanged,
),
),
),
Expanded(
child: Text(translate('More')),
)
],
)),
if (showFps)
Obx(() => Row(
children: [
Expanded(
flex: 3,
child: Slider(
value: fpsValue.value,
min: 5.0,
max: 120.0,
divisions: 23,
onChanged: (double value) async {
fpsValue.value = value;
debouncerFps.value = value;
},
),
),
Expanded(
flex: 1,
child: Text(
'${fpsValue.value.round()}',
style: const TextStyle(fontSize: 15),
)),
Expanded(
flex: 2,
child: Text(
translate('FPS'),
style: const TextStyle(fontSize: 15),
))
],
)),
],
);
}
customImageQualitySetting() {
final qualityKey = 'custom_image_quality';
final fpsKey = 'custom-fps';
var initQuality =
(double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ?? 50.0);
if (initQuality < 10 || initQuality > 2000) {
initQuality = 50;
}
var initFps =
(double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ?? 30.0);
if (initFps < 5 || initFps > 120) {
initFps = 30;
}
return customImageQualityWidget(
initQuality: initQuality,
initFps: initFps,
setQuality: (v) {
bind.mainSetUserDefaultOption(key: qualityKey, value: v.toString());
},
setFps: (v) {
bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString());
},
showFps: true);
}
Future<bool> setServerConfig(
List<TextEditingController> controllers,
List<RxString> errMsgs,
ServerConfig config,
) async {
config.idServer = config.idServer.trim();
config.relayServer = config.relayServer.trim();
config.apiServer = config.apiServer.trim();
config.key = config.key.trim();
// id
if (config.idServer.isNotEmpty) {
errMsgs[0].value =
translate(await bind.mainTestIfValidServer(server: config.idServer));
if (errMsgs[0].isNotEmpty) {
return false;
}
}
// relay
if (config.relayServer.isNotEmpty) {
errMsgs[1].value =
translate(await bind.mainTestIfValidServer(server: config.relayServer));
if (errMsgs[1].isNotEmpty) {
return false;
}
}
// api
if (config.apiServer.isNotEmpty) {
if (!config.apiServer.startsWith('http://') &&
!config.apiServer.startsWith('https://')) {
errMsgs[2].value =
'${translate("API Server")}: ${translate("invalid_http")}';
return false;
}
}
final oldApiServer = await bind.mainGetApiServer();
// should set one by one
await bind.mainSetOption(
key: 'custom-rendezvous-server', value: config.idServer);
await bind.mainSetOption(key: 'relay-server', value: config.relayServer);
await bind.mainSetOption(key: 'api-server', value: config.apiServer);
await bind.mainSetOption(key: 'key', value: config.key);
final newApiServer = await bind.mainGetApiServer();
if (oldApiServer.isNotEmpty &&
oldApiServer != newApiServer &&
gFFI.userModel.isLogin) {
gFFI.userModel.logOut(apiServer: oldApiServer);
}
return true;
}
List<Widget> ServerConfigImportExportWidgets(
List<TextEditingController> controllers,
List<RxString> errMsgs,
) {
import() {
Clipboard.getData(Clipboard.kTextPlain).then((value) {
final text = value?.text;
if (text != null && text.isNotEmpty) {
try {
final sc = ServerConfig.decode(text);
if (sc.idServer.isNotEmpty) {
controllers[0].text = sc.idServer;
controllers[1].text = sc.relayServer;
controllers[2].text = sc.apiServer;
controllers[3].text = sc.key;
Future<bool> success = setServerConfig(controllers, errMsgs, sc);
success.then((value) {
if (value) {
showToast(
translate('Import server configuration successfully'));
} else {
showToast(translate('Invalid server configuration'));
}
});
} else {
showToast(translate('Invalid server configuration'));
}
} catch (e) {
showToast(translate('Invalid server configuration'));
}
} else {
showToast(translate('Clipboard is empty'));
}
});
}
export() {
final text = ServerConfig(
idServer: controllers[0].text.trim(),
relayServer: controllers[1].text.trim(),
apiServer: controllers[2].text.trim(),
key: controllers[3].text.trim())
.encode();
debugPrint("ServerConfig export: $text");
Clipboard.setData(ClipboardData(text: text));
showToast(translate('Export server configuration successfully'));
}
return [
Tooltip(
message: translate('Import Server Config'),
child: IconButton(
icon: Icon(Icons.paste, color: Colors.grey), onPressed: import),
),
Tooltip(
message: translate('Export Server Config'),
child: IconButton(
icon: Icon(Icons.copy, color: Colors.grey), onPressed: export))
];
}

View File

@ -5,6 +5,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
@ -965,54 +966,27 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
var relayController = TextEditingController(text: old('relay-server')); var relayController = TextEditingController(text: old('relay-server'));
var apiController = TextEditingController(text: old('api-server')); var apiController = TextEditingController(text: old('api-server'));
var keyController = TextEditingController(text: old('key')); var keyController = TextEditingController(text: old('key'));
final controllers = [
set(String idServer, String relayServer, String apiServer, idController,
String key) async { relayController,
idServer = idServer.trim(); apiController,
relayServer = relayServer.trim(); keyController,
apiServer = apiServer.trim(); ];
key = key.trim(); final errMsgs = [
if (idServer.isNotEmpty) { idErrMsg,
idErrMsg.value = relayErrMsg,
translate(await bind.mainTestIfValidServer(server: idServer)); apiErrMsg,
if (idErrMsg.isNotEmpty) { ];
return false;
}
}
if (relayServer.isNotEmpty) {
relayErrMsg.value =
translate(await bind.mainTestIfValidServer(server: relayServer));
if (relayErrMsg.isNotEmpty) {
return false;
}
}
if (apiServer.isNotEmpty) {
if (!apiServer.startsWith('http://') &&
!apiServer.startsWith('https://')) {
apiErrMsg.value =
'${translate("API Server")}: ${translate("invalid_http")}';
return false;
}
}
final oldApiServer = await bind.mainGetApiServer();
// should set one by one
await bind.mainSetOption(
key: 'custom-rendezvous-server', value: idServer);
await bind.mainSetOption(key: 'relay-server', value: relayServer);
await bind.mainSetOption(key: 'api-server', value: apiServer);
await bind.mainSetOption(key: 'key', value: key);
final newApiServer = await bind.mainGetApiServer();
if (oldApiServer.isNotEmpty && oldApiServer != newApiServer) {
await gFFI.userModel.logOut(apiServer: oldApiServer);
}
return true;
}
submit() async { submit() async {
bool result = await set(idController.text, relayController.text, bool result = await setServerConfig(
apiController.text, keyController.text); controllers,
errMsgs,
ServerConfig(
idServer: idController.text,
relayServer: relayController.text,
apiServer: apiController.text,
key: keyController.text));
if (result) { if (result) {
setState(() {}); setState(() {});
showToast(translate('Successful')); showToast(translate('Successful'));
@ -1021,83 +995,28 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
} }
} }
import() {
Clipboard.getData(Clipboard.kTextPlain).then((value) {
final text = value?.text;
if (text != null && text.isNotEmpty) {
try {
final sc = ServerConfig.decode(text);
if (sc.idServer.isNotEmpty) {
idController.text = sc.idServer;
relayController.text = sc.relayServer;
apiController.text = sc.apiServer;
keyController.text = sc.key;
Future<bool> success =
set(sc.idServer, sc.relayServer, sc.apiServer, sc.key);
success.then((value) {
if (value) {
showToast(
translate('Import server configuration successfully'));
} else {
showToast(translate('Invalid server configuration'));
}
});
} else {
showToast(translate('Invalid server configuration'));
}
} catch (e) {
showToast(translate('Invalid server configuration'));
}
} else {
showToast(translate('Clipboard is empty'));
}
});
}
export() {
final text = ServerConfig(
idServer: idController.text,
relayServer: relayController.text,
apiServer: apiController.text,
key: keyController.text)
.encode();
debugPrint("ServerConfig export: $text");
Clipboard.setData(ClipboardData(text: text));
showToast(translate('Export server configuration successfully'));
}
bool secure = !enabled; bool secure = !enabled;
return _Card(title: 'ID/Relay Server', title_suffix: [ return _Card(
Tooltip( title: 'ID/Relay Server',
message: translate('Import Server Config'), title_suffix: ServerConfigImportExportWidgets(controllers, errMsgs),
child: IconButton(
icon: Icon(Icons.paste, color: Colors.grey),
onPressed: enabled ? import : null),
),
Tooltip(
message: translate('Export Server Config'),
child: IconButton(
icon: Icon(Icons.copy, color: Colors.grey),
onPressed: enabled ? export : null)),
], children: [
Column(
children: [ children: [
Obx(() => _LabeledTextField(context, 'ID Server', idController, Column(
idErrMsg.value, enabled, secure)), children: [
Obx(() => _LabeledTextField(context, 'Relay Server', Obx(() => _LabeledTextField(context, 'ID Server', idController,
relayController, relayErrMsg.value, enabled, secure)), idErrMsg.value, enabled, secure)),
Obx(() => _LabeledTextField(context, 'API Server', apiController, Obx(() => _LabeledTextField(context, 'Relay Server',
apiErrMsg.value, enabled, secure)), relayController, relayErrMsg.value, enabled, secure)),
_LabeledTextField( Obx(() => _LabeledTextField(context, 'API Server',
context, 'Key', keyController, '', enabled, secure), apiController, apiErrMsg.value, enabled, secure)),
Row( _LabeledTextField(
mainAxisAlignment: MainAxisAlignment.end, context, 'Key', keyController, '', enabled, secure),
children: [_Button('Apply', submit, enabled: enabled)], Row(
).marginOnly(top: 10), mainAxisAlignment: MainAxisAlignment.end,
], children: [_Button('Apply', submit, enabled: enabled)],
) ).marginOnly(top: 10),
]); ],
)
]);
} }
return tmpWrapper(); return tmpWrapper();
@ -1181,15 +1100,6 @@ class _DisplayState extends State<_Display> {
} }
final groupValue = bind.mainGetUserDefaultOption(key: key); final groupValue = bind.mainGetUserDefaultOption(key: key);
final qualityKey = 'custom_image_quality';
final qualityValue =
(double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ??
50.0)
.obs;
final fpsKey = 'custom-fps';
final fpsValue =
(double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ?? 30.0)
.obs;
return _Card(title: 'Default Image Quality', children: [ return _Card(title: 'Default Image Quality', children: [
_Radio(context, _Radio(context,
value: kRemoteImageQualityBest, value: kRemoteImageQualityBest,
@ -1213,64 +1123,7 @@ class _DisplayState extends State<_Display> {
onChanged: onChanged), onChanged: onChanged),
Offstage( Offstage(
offstage: groupValue != kRemoteImageQualityCustom, offstage: groupValue != kRemoteImageQualityCustom,
child: Column( child: customImageQualitySetting(),
children: [
Obx(() => Row(
children: [
Slider(
value: qualityValue.value,
min: 10.0,
max: 100.0,
divisions: 18,
onChanged: (double value) async {
qualityValue.value = value;
await bind.mainSetUserDefaultOption(
key: qualityKey, value: value.toString());
},
),
SizedBox(
width: 40,
child: Text(
'${qualityValue.value.round()}%',
style: const TextStyle(fontSize: 15),
)),
SizedBox(
width: 50,
child: Text(
translate('Bitrate'),
style: const TextStyle(fontSize: 15),
))
],
)),
Obx(() => Row(
children: [
Slider(
value: fpsValue.value,
min: 5.0,
max: 120.0,
divisions: 23,
onChanged: (double value) async {
fpsValue.value = value;
await bind.mainSetUserDefaultOption(
key: fpsKey, value: value.toString());
},
),
SizedBox(
width: 40,
child: Text(
'${fpsValue.value.round()}',
style: const TextStyle(fontSize: 15),
)),
SizedBox(
width: 50,
child: Text(
translate('FPS'),
style: const TextStyle(fontSize: 15),
))
],
)),
],
),
) )
]); ]);
} }

View File

@ -398,7 +398,7 @@ class _AppState extends State<App> {
themeMode: MyTheme.currentThemeMode(), themeMode: MyTheme.currentThemeMode(),
home: isDesktop home: isDesktop
? const DesktopTabPage() ? const DesktopTabPage()
: !isAndroid : isWeb
? WebHomePage() ? WebHomePage()
: HomePage(), : HomePage(),
localizationsDelegates: const [ localizationsDelegates: const [

View File

@ -28,7 +28,7 @@ class ConnectionPage extends StatefulWidget implements PageShape {
final title = translate("Connection"); final title = translate("Connection");
@override @override
final appBarActions = !isAndroid ? <Widget>[const WebMenu()] : <Widget>[]; final appBarActions = isWeb ? <Widget>[const WebMenu()] : <Widget>[];
@override @override
State<ConnectionPage> createState() => _ConnectionPageState(); State<ConnectionPage> createState() => _ConnectionPageState();
@ -211,25 +211,6 @@ class WebMenu extends StatefulWidget {
} }
class _WebMenuState extends State<WebMenu> { class _WebMenuState extends State<WebMenu> {
String url = "";
@override
void initState() {
super.initState();
() async {
final urlRes = await bind.mainGetApiServer();
var update = false;
if (urlRes != url) {
url = urlRes;
update = true;
}
if (update) {
setState(() {});
}
}();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Provider.of<FfiModel>(context); Provider.of<FfiModel>(context);
@ -251,16 +232,14 @@ class _WebMenuState extends State<WebMenu> {
child: Text(translate('ID/Relay Server')), child: Text(translate('ID/Relay Server')),
) )
] + ] +
(url.contains('admin.rustdesk.com') [
? <PopupMenuItem<String>>[] PopupMenuItem(
: [ value: "login",
PopupMenuItem( child: Text(gFFI.userModel.userName.value.isEmpty
value: "login", ? translate("Login")
child: Text(gFFI.userModel.userName.value.isEmpty : '${translate("Logout")} (${gFFI.userModel.userName.value})'),
? translate("Login") )
: '${translate("Logout")} (${gFFI.userModel.userName.value})'), ] +
)
]) +
[ [
PopupMenuItem( PopupMenuItem(
value: "about", value: "about",

View File

@ -494,7 +494,7 @@ class _RemotePageState extends State<RemotePage> {
gFFI.ffiModel.toggleTouchMode(); gFFI.ffiModel.toggleTouchMode();
final v = gFFI.ffiModel.touchMode ? 'Y' : ''; final v = gFFI.ffiModel.touchMode ? 'Y' : '';
bind.sessionPeerOption( bind.sessionPeerOption(
sessionId: sessionId, name: "touch", value: v); sessionId: sessionId, name: "touch-mode", value: v);
}))); })));
} }

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:settings_ui/settings_ui.dart'; import 'package:settings_ui/settings_ui.dart';
@ -383,7 +383,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
SettingsSection( SettingsSection(
title: Text(translate('Account')), title: Text(translate('Account')),
tiles: [ tiles: [
SettingsTile.navigation( SettingsTile(
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
? translate('Login') ? translate('Login')
: '${translate('Logout')} (${gFFI.userModel.userName.value})')), : '${translate('Logout')} (${gFFI.userModel.userName.value})')),
@ -399,19 +399,19 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
], ],
), ),
SettingsSection(title: Text(translate("Settings")), tiles: [ SettingsSection(title: Text(translate("Settings")), tiles: [
SettingsTile.navigation( SettingsTile(
title: Text(translate('ID/Relay Server')), title: Text(translate('ID/Relay Server')),
leading: Icon(Icons.cloud), leading: Icon(Icons.cloud),
onPressed: (context) { onPressed: (context) {
showServerSettings(gFFI.dialogManager); showServerSettings(gFFI.dialogManager);
}), }),
SettingsTile.navigation( SettingsTile(
title: Text(translate('Language')), title: Text(translate('Language')),
leading: Icon(Icons.translate), leading: Icon(Icons.translate),
onPressed: (context) { onPressed: (context) {
showLanguageSettings(gFFI.dialogManager); showLanguageSettings(gFFI.dialogManager);
}), }),
SettingsTile.navigation( SettingsTile(
title: Text(translate( title: Text(translate(
Theme.of(context).brightness == Brightness.light Theme.of(context).brightness == Brightness.light
? 'Dark Theme' ? 'Dark Theme'
@ -424,45 +424,50 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
}, },
) )
]), ]),
SettingsSection( if (isAndroid)
title: Text(translate("Recording")), SettingsSection(
tiles: [ title: Text(translate("Recording")),
SettingsTile.switchTile( tiles: [
title: Text(translate('Automatically record incoming sessions')), SettingsTile.switchTile(
leading: Icon(Icons.videocam), title:
description: FutureBuilder( Text(translate('Automatically record incoming sessions')),
builder: (ctx, data) => Offstage( leading: Icon(Icons.videocam),
offstage: !data.hasData, description: FutureBuilder(
child: Text("${translate("Directory")}: ${data.data}")), builder: (ctx, data) => Offstage(
future: bind.mainDefaultVideoSaveDirectory()), offstage: !data.hasData,
initialValue: _autoRecordIncomingSession, child: Text("${translate("Directory")}: ${data.data}")),
onToggle: (v) async { future: bind.mainDefaultVideoSaveDirectory()),
await bind.mainSetOption( initialValue: _autoRecordIncomingSession,
key: "allow-auto-record-incoming", onToggle: (v) async {
value: bool2option("allow-auto-record-incoming", v)); await bind.mainSetOption(
final newValue = option2bool( key: "allow-auto-record-incoming",
'allow-auto-record-incoming', value: bool2option("allow-auto-record-incoming", v));
await bind.mainGetOption( final newValue = option2bool(
key: 'allow-auto-record-incoming')); 'allow-auto-record-incoming',
setState(() { await bind.mainGetOption(
_autoRecordIncomingSession = newValue; key: 'allow-auto-record-incoming'));
}); setState(() {
}, _autoRecordIncomingSession = newValue;
), });
], },
), ),
SettingsSection( ],
title: Text(translate("Share Screen")), ),
tiles: shareScreenTiles, if (isAndroid)
), SettingsSection(
SettingsSection( title: Text(translate("Share Screen")),
title: Text(translate("Enhancements")), tiles: shareScreenTiles,
tiles: enhancementsTiles, ),
), defaultDisplaySection(),
if (isAndroid)
SettingsSection(
title: Text(translate("Enhancements")),
tiles: enhancementsTiles,
),
SettingsSection( SettingsSection(
title: Text(translate("About")), title: Text(translate("About")),
tiles: [ tiles: [
SettingsTile.navigation( SettingsTile(
onPressed: (context) async { onPressed: (context) async {
if (await canLaunchUrl(Uri.parse(url))) { if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url)); await launchUrl(Uri.parse(url));
@ -477,21 +482,22 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
)), )),
), ),
leading: Icon(Icons.info)), leading: Icon(Icons.info)),
SettingsTile.navigation( SettingsTile(
title: Text(translate("Build Date")), title: Text(translate("Build Date")),
value: Padding( value: Padding(
padding: EdgeInsets.symmetric(vertical: 8), padding: EdgeInsets.symmetric(vertical: 8),
child: Text(_buildDate), child: Text(_buildDate),
), ),
leading: Icon(Icons.query_builder)), leading: Icon(Icons.query_builder)),
SettingsTile.navigation( if (isAndroid)
onPressed: (context) => onCopyFingerprint(_fingerprint), SettingsTile(
title: Text(translate("Fingerprint")), onPressed: (context) => onCopyFingerprint(_fingerprint),
value: Padding( title: Text(translate("Fingerprint")),
padding: EdgeInsets.symmetric(vertical: 8), value: Padding(
child: Text(_fingerprint), padding: EdgeInsets.symmetric(vertical: 8),
), child: Text(_fingerprint),
leading: Icon(Icons.fingerprint)), ),
leading: Icon(Icons.fingerprint)),
], ],
), ),
], ],
@ -508,6 +514,23 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
} }
return true; return true;
} }
defaultDisplaySection() {
return SettingsSection(
title: Text(translate("Display Settings")),
tiles: [
SettingsTile(
title: Text(translate('Display Settings')),
leading: Icon(Icons.desktop_windows_outlined),
trailing: Icon(Icons.arrow_forward_ios),
onPressed: (context) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _DisplayPage();
}));
})
],
);
}
} }
void showServerSettings(OverlayDialogManager dialogManager) async { void showServerSettings(OverlayDialogManager dialogManager) async {
@ -618,3 +641,181 @@ class ScanButton extends StatelessWidget {
); );
} }
} }
class _DisplayPage extends StatefulWidget {
const _DisplayPage({super.key});
@override
State<_DisplayPage> createState() => __DisplayPageState();
}
class __DisplayPageState extends State<_DisplayPage> {
@override
Widget build(BuildContext context) {
final Map codecsJson = jsonDecode(bind.mainSupportedHwdecodings());
final h264 = codecsJson['h264'] ?? false;
final h265 = codecsJson['h265'] ?? false;
var codecList = [
_RadioEntry('Auto', 'auto'),
_RadioEntry('VP8', 'vp8'),
_RadioEntry('VP9', 'vp9'),
_RadioEntry('AV1', 'av1'),
if (h264) _RadioEntry('H264', 'h264'),
if (h265) _RadioEntry('H265', 'h265')
];
RxBool showCustomImageQuality = false.obs;
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: Icon(Icons.arrow_back_ios)),
title: Text(translate('Display Settings')),
centerTitle: true,
),
body: SettingsList(sections: [
SettingsSection(
tiles: [
_getPopupDialogRadioEntry(
title: 'Default View Style',
list: [
_RadioEntry('Scale original', kRemoteViewStyleOriginal),
_RadioEntry('Scale adaptive', kRemoteViewStyleAdaptive)
],
getter: () => bind.mainGetUserDefaultOption(key: 'view_style'),
asyncSetter: (value) async {
await bind.mainSetUserDefaultOption(
key: 'view_style', value: value);
},
),
_getPopupDialogRadioEntry(
title: 'Default Image Quality',
list: [
_RadioEntry('Good image quality', kRemoteImageQualityBest),
_RadioEntry('Balanced', kRemoteImageQualityBalanced),
_RadioEntry('Optimize reaction time', kRemoteImageQualityLow),
_RadioEntry('Custom', kRemoteImageQualityCustom),
],
getter: () {
final v = bind.mainGetUserDefaultOption(key: 'image_quality');
showCustomImageQuality.value = v == kRemoteImageQualityCustom;
return v;
},
asyncSetter: (value) async {
await bind.mainSetUserDefaultOption(
key: 'image_quality', value: value);
showCustomImageQuality.value =
value == kRemoteImageQualityCustom;
},
tail: customImageQualitySetting(),
showTail: showCustomImageQuality,
notCloseValue: kRemoteImageQualityCustom,
),
_getPopupDialogRadioEntry(
title: 'Default Codec',
list: codecList,
getter: () =>
bind.mainGetUserDefaultOption(key: 'codec-preference'),
asyncSetter: (value) async {
await bind.mainSetUserDefaultOption(
key: 'codec-preference', value: value);
},
),
],
),
SettingsSection(
title: Text(translate('Other Default Options')),
tiles: [
otherRow('Show remote cursor', 'show_remote_cursor'),
otherRow('Show quality monitor', 'show_quality_monitor'),
otherRow('Mute', 'disable_audio'),
otherRow('Disable clipboard', 'disable_clipboard'),
otherRow('Lock after session end', 'lock_after_session_end'),
otherRow('Privacy mode', 'privacy_mode'),
otherRow('Touch mode', 'touch-mode'),
],
),
]),
);
}
otherRow(String label, String key) {
final value = bind.mainGetUserDefaultOption(key: key) == 'Y';
return SettingsTile.switchTile(
initialValue: value,
title: Text(translate(label)),
onToggle: (b) async {
await bind.mainSetUserDefaultOption(key: key, value: b ? 'Y' : '');
setState(() {});
},
);
}
}
class _RadioEntry {
final String label;
final String value;
_RadioEntry(this.label, this.value);
}
typedef _RadioEntryGetter = String Function();
typedef _RadioEntrySetter = Future<void> Function(String);
_getPopupDialogRadioEntry({
required String title,
required List<_RadioEntry> list,
required _RadioEntryGetter getter,
required _RadioEntrySetter asyncSetter,
Widget? tail,
RxBool? showTail,
String? notCloseValue,
}) {
RxString groupValue = ''.obs;
RxString valueText = ''.obs;
init() {
groupValue.value = getter();
final e = list.firstWhereOrNull((e) => e.value == groupValue.value);
if (e != null) {
valueText.value = e.label;
}
}
init();
void showDialog() async {
gFFI.dialogManager.show((setState, close, context) {
onChanged(String? value) async {
if (value == null) return;
await asyncSetter(value);
init();
if (value != notCloseValue) {
close();
}
}
return CustomAlertDialog(
content: Obx(
() => Column(children: [
...list
.map((e) => getRadio(Text(translate(e.label)), e.value,
groupValue.value, (String? value) => onChanged(value)))
.toList(),
Offstage(
offstage:
!(tail != null && showTail != null && showTail.value == true),
child: tail,
),
]),
));
}, backDismiss: true, clickMaskDismiss: true);
}
return SettingsTile(
title: Text(translate(title)),
onPressed: (context) => showDialog(),
value: Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Obx(() => Text(translate(valueText.value))),
),
);
}

View File

@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../common.dart'; import '../../common.dart';
@ -147,59 +147,72 @@ void setTemporaryPasswordLengthDialog(
void showServerSettingsWithValue( void showServerSettingsWithValue(
ServerConfig serverConfig, OverlayDialogManager dialogManager) async { ServerConfig serverConfig, OverlayDialogManager dialogManager) async {
Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
final oldCfg = ServerConfig.fromOptions(oldOptions);
var isInProgress = false; var isInProgress = false;
final idCtrl = TextEditingController(text: serverConfig.idServer); final idCtrl = TextEditingController(text: serverConfig.idServer);
final relayCtrl = TextEditingController(text: serverConfig.relayServer); final relayCtrl = TextEditingController(text: serverConfig.relayServer);
final apiCtrl = TextEditingController(text: serverConfig.apiServer); final apiCtrl = TextEditingController(text: serverConfig.apiServer);
final keyCtrl = TextEditingController(text: serverConfig.key); final keyCtrl = TextEditingController(text: serverConfig.key);
String? idServerMsg; RxString idServerMsg = ''.obs;
String? relayServerMsg; RxString relayServerMsg = ''.obs;
String? apiServerMsg; RxString apiServerMsg = ''.obs;
final controllers = [idCtrl, relayCtrl, apiCtrl, keyCtrl];
final errMsgs = [
idServerMsg,
relayServerMsg,
apiServerMsg,
];
dialogManager.show((setState, close, context) { dialogManager.show((setState, close, context) {
Future<bool> validate() async { Future<bool> submit() async {
if (idCtrl.text != oldCfg.idServer) { setState(() {
final res = await validateAsync(idCtrl.text); isInProgress = true;
setState(() => idServerMsg = res); });
if (idServerMsg != null) return false; bool ret = await setServerConfig(
} controllers,
if (relayCtrl.text != oldCfg.relayServer) { errMsgs,
relayServerMsg = await validateAsync(relayCtrl.text); ServerConfig(
if (relayServerMsg != null) return false; idServer: idCtrl.text.trim(),
} relayServer: relayCtrl.text.trim(),
if (apiCtrl.text != oldCfg.apiServer) { apiServer: apiCtrl.text.trim(),
if (apiServerMsg != null) return false; key: keyCtrl.text.trim()));
} setState(() {
return true; isInProgress = false;
});
return ret;
} }
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate('ID/Relay Server')), title: Row(
children: [
Expanded(child: Text(translate('ID/Relay Server'))),
...ServerConfigImportExportWidgets(controllers, errMsgs),
],
),
content: Form( content: Form(
child: Column( child: Obx(() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
TextFormField( TextFormField(
controller: idCtrl, controller: idCtrl,
decoration: InputDecoration( decoration: InputDecoration(
labelText: translate('ID Server'), labelText: translate('ID Server'),
errorText: idServerMsg), errorText: idServerMsg.value.isEmpty
? null
: idServerMsg.value),
)
] +
[
TextFormField(
controller: relayCtrl,
decoration: InputDecoration(
labelText: translate('Relay Server'),
errorText: relayServerMsg.value.isEmpty
? null
: relayServerMsg.value),
) )
] + ] +
(isAndroid
? [
TextFormField(
controller: relayCtrl,
decoration: InputDecoration(
labelText: translate('Relay Server'),
errorText: relayServerMsg),
)
]
: []) +
[ [
TextFormField( TextFormField(
controller: apiCtrl, controller: apiCtrl,
@ -214,7 +227,7 @@ void showServerSettingsWithValue(
return translate("invalid_http"); return translate("invalid_http");
} }
} }
return apiServerMsg; return null;
}, },
), ),
TextFormField( TextFormField(
@ -225,7 +238,7 @@ void showServerSettingsWithValue(
), ),
// NOT use Offstage to wrap LinearProgressIndicator // NOT use Offstage to wrap LinearProgressIndicator
if (isInProgress) const LinearProgressIndicator(), if (isInProgress) const LinearProgressIndicator(),
])), ]))),
actions: [ actions: [
dialogButton('Cancel', onPressed: () { dialogButton('Cancel', onPressed: () {
close(); close();
@ -233,35 +246,12 @@ void showServerSettingsWithValue(
dialogButton( dialogButton(
'OK', 'OK',
onPressed: () async { onPressed: () async {
setState(() { if (await submit()) {
idServerMsg = null;
relayServerMsg = null;
apiServerMsg = null;
isInProgress = true;
});
if (await validate()) {
if (idCtrl.text != oldCfg.idServer) {
if (oldCfg.idServer.isNotEmpty) {
await gFFI.userModel.logOut();
}
bind.mainSetOption(
key: "custom-rendezvous-server", value: idCtrl.text);
}
if (relayCtrl.text != oldCfg.relayServer) {
bind.mainSetOption(key: "relay-server", value: relayCtrl.text);
}
if (keyCtrl.text != oldCfg.key) {
bind.mainSetOption(key: "key", value: keyCtrl.text);
}
if (apiCtrl.text != oldCfg.apiServer) {
bind.mainSetOption(key: "api-server", value: apiCtrl.text);
}
close(); close();
showToast(translate('Successful')); showToast(translate('Successful'));
} else {
showToast(translate('Failed'));
} }
setState(() {
isInProgress = false;
});
}, },
), ),
], ],

View File

@ -63,7 +63,7 @@ class ChatModel with ChangeNotifier {
bool isConnManager = false; bool isConnManager = false;
RxBool isWindowFocus = true.obs; RxBool isWindowFocus = true.obs;
BlockableOverlayState? _blockableOverlayState; BlockableOverlayState _blockableOverlayState = BlockableOverlayState();
final Rx<VoiceCallStatus> _voiceCallStatus = Rx(VoiceCallStatus.notStarted); final Rx<VoiceCallStatus> _voiceCallStatus = Rx(VoiceCallStatus.notStarted);
Rx<VoiceCallStatus> get voiceCallStatus => _voiceCallStatus; Rx<VoiceCallStatus> get voiceCallStatus => _voiceCallStatus;
@ -154,7 +154,7 @@ class ChatModel with ChangeNotifier {
} }
} }
final overlayState = _blockableOverlayState?.state; final overlayState = _blockableOverlayState.state;
if (overlayState == null) return; if (overlayState == null) return;
final overlay = OverlayEntry(builder: (context) { final overlay = OverlayEntry(builder: (context) {

View File

@ -1815,7 +1815,7 @@ class FFI {
} else { } else {
// Fetch the image buffer from rust codes. // Fetch the image buffer from rust codes.
final sz = platformFFI.getRgbaSize(sessionId); final sz = platformFFI.getRgbaSize(sessionId);
if (sz == null || sz == 0) { if (sz == 0) {
return; return;
} }
final rgba = platformFFI.getRgba(sessionId, sz); final rgba = platformFFI.getRgba(sessionId, sz);

View File

@ -21,16 +21,8 @@ class RgbaFrame extends Struct {
external Pointer<Uint8> data; external Pointer<Uint8> data;
} }
typedef F2 = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);
typedef F3 = Pointer<Uint8> Function(Pointer<Utf8>); typedef F3 = Pointer<Uint8> Function(Pointer<Utf8>);
typedef F4 = Uint64 Function(Pointer<Utf8>);
typedef F4Dart = int Function(Pointer<Utf8>);
typedef F5 = Void Function(Pointer<Utf8>);
typedef F5Dart = void Function(Pointer<Utf8>);
typedef HandleEvent = Future<void> Function(Map<String, dynamic> evt); typedef HandleEvent = Future<void> Function(Map<String, dynamic> evt);
// pub fn session_register_texture(id: *const char, ptr: usize)
typedef F6 = Void Function(Pointer<Utf8>, Uint64);
typedef F6Dart = void Function(Pointer<Utf8>, int);
/// FFI wrapper around the native Rust core. /// FFI wrapper around the native Rust core.
/// Hides the platform differences. /// Hides the platform differences.
@ -38,7 +30,6 @@ class PlatformFFI {
String _dir = ''; String _dir = '';
// _homeDir is only needed for Android and IOS. // _homeDir is only needed for Android and IOS.
String _homeDir = ''; String _homeDir = '';
F2? _translate;
final _eventHandlers = <String, Map<String, HandleEvent>>{}; final _eventHandlers = <String, Map<String, HandleEvent>>{};
late RustdeskImpl _ffiBind; late RustdeskImpl _ffiBind;
late String _appType; late String _appType;
@ -51,9 +42,6 @@ class PlatformFFI {
RustdeskImpl get ffiBind => _ffiBind; RustdeskImpl get ffiBind => _ffiBind;
F3? _session_get_rgba; F3? _session_get_rgba;
F4Dart? _session_get_rgba_size;
F5Dart? _session_next_rgba;
F6Dart? _session_register_texture;
static get localeName => Platform.localeName; static get localeName => Platform.localeName;
@ -89,18 +77,8 @@ class PlatformFFI {
} }
} }
String translate(String name, String locale) { String translate(String name, String locale) =>
if (_translate == null) return name; _ffiBind.translate(name: name, locale: locale);
var a = name.toNativeUtf8();
var b = locale.toNativeUtf8();
var p = _translate!(a, b);
assert(p != nullptr);
final res = p.toDartString();
calloc.free(p);
calloc.free(a);
calloc.free(b);
return res;
}
Uint8List? getRgba(SessionID sessionId, int bufSize) { Uint8List? getRgba(SessionID sessionId, int bufSize) {
if (_session_get_rgba == null) return null; if (_session_get_rgba == null) return null;
@ -118,30 +96,11 @@ class PlatformFFI {
} }
} }
int? getRgbaSize(SessionID sessionId) { int getRgbaSize(SessionID sessionId) =>
if (_session_get_rgba_size == null) return null; _ffiBind.sessionGetRgbaSize(sessionId: sessionId);
final sessionIdStr = sessionId.toString(); void nextRgba(SessionID sessionId) => _ffiBind.sessionNextRgba(sessionId: sessionId);
var a = sessionIdStr.toNativeUtf8(); void registerTexture(SessionID sessionId, int ptr) =>
final bufferSize = _session_get_rgba_size!(a); _ffiBind.sessionRegisterTexture(sessionId: sessionId, ptr: ptr);
malloc.free(a);
return bufferSize;
}
void nextRgba(SessionID sessionId) {
if (_session_next_rgba == null) return;
final sessionIdStr = sessionId.toString();
final a = sessionIdStr.toNativeUtf8();
_session_next_rgba!(a);
malloc.free(a);
}
void registerTexture(SessionID sessionId, int ptr) {
if (_session_register_texture == null) return;
final sessionIdStr = sessionId.toString();
final a = sessionIdStr.toNativeUtf8();
_session_register_texture!(a, ptr);
malloc.free(a);
}
/// Init the FFI class, loads the native Rust core library. /// Init the FFI class, loads the native Rust core library.
Future<void> init(String appType) async { Future<void> init(String appType) async {
@ -157,14 +116,7 @@ class PlatformFFI {
: DynamicLibrary.process(); : DynamicLibrary.process();
debugPrint('initializing FFI $_appType'); debugPrint('initializing FFI $_appType');
try { try {
_translate = dylib.lookupFunction<F2, F2>('translate');
_session_get_rgba = dylib.lookupFunction<F3, F3>("session_get_rgba"); _session_get_rgba = dylib.lookupFunction<F3, F3>("session_get_rgba");
_session_get_rgba_size =
dylib.lookupFunction<F4, F4Dart>("session_get_rgba_size");
_session_next_rgba =
dylib.lookupFunction<F5, F5Dart>("session_next_rgba");
_session_register_texture =
dylib.lookupFunction<F6, F6Dart>("session_register_texture");
try { try {
// SYSTEM user failed // SYSTEM user failed
_dir = (await getApplicationDocumentsDirectory()).path; _dir = (await getApplicationDocumentsDirectory()).path;

View File

@ -40,8 +40,6 @@ class ServerModel with ChangeNotifier {
late String _emptyIdShow; late String _emptyIdShow;
late final IDTextEditingController _serverId; late final IDTextEditingController _serverId;
final _serverPasswd =
TextEditingController(text: translate("Generating ..."));
final tabController = DesktopTabController(tabType: DesktopTabType.cm); final tabController = DesktopTabController(tabType: DesktopTabType.cm);
@ -63,6 +61,9 @@ class ServerModel with ChangeNotifier {
int get connectStatus => _connectStatus; int get connectStatus => _connectStatus;
TextEditingController get _serverPasswd =>
TextEditingController(text: translate("Generating ..."));
String get verificationMethod { String get verificationMethod {
final index = [ final index = [
kUseTemporaryPassword, kUseTemporaryPassword,

View File

@ -237,10 +237,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.2" version: "1.17.1"
colorize: colorize:
dependency: transitive dependency: transitive
description: description:
@ -743,10 +743,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: intl name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.18.1" version: "0.18.0"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -799,10 +799,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.2.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -1474,14 +1474,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
@ -1565,5 +1557,5 @@ packages:
source: hosted source: hosted
version: "0.2.0" version: "0.2.0"
sdks: sdks:
dart: ">=3.1.0-185.0.dev <4.0.0" dart: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0-0" flutter: ">=3.7.0-0"

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers # 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
version: 1.2.3 version: 1.2.3+39
environment: environment:
sdk: ">=2.17.0" sdk: ">=2.17.0"

View File

@ -214,7 +214,7 @@ pub struct Resolution {
pub h: i32, pub h: i32,
} }
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PeerConfig { pub struct PeerConfig {
#[serde(default, deserialize_with = "deserialize_vec_u8")] #[serde(default, deserialize_with = "deserialize_vec_u8")]
pub password: Vec<u8>, pub password: Vec<u8>,
@ -296,6 +296,38 @@ pub struct PeerConfig {
pub transfer: TransferSerde, pub transfer: TransferSerde,
} }
impl Default for PeerConfig {
fn default() -> Self {
Self {
password: Default::default(),
size: Default::default(),
size_ft: Default::default(),
size_pf: Default::default(),
view_style: Self::default_view_style(),
scroll_style: Self::default_scroll_style(),
image_quality: Self::default_image_quality(),
custom_image_quality: Self::default_custom_image_quality(),
show_remote_cursor: Default::default(),
lock_after_session_end: Default::default(),
privacy_mode: Default::default(),
allow_swap_key: Default::default(),
port_forwards: Default::default(),
direct_failures: Default::default(),
disable_audio: Default::default(),
disable_clipboard: Default::default(),
enable_file_transfer: Default::default(),
show_quality_monitor: Default::default(),
keyboard_mode: Default::default(),
view_only: Default::default(),
custom_resolutions: Default::default(),
options: Self::default_options(),
ui_flutter: Default::default(),
info: Default::default(),
transfer: Default::default(),
}
}
}
#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)] #[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)]
pub struct PeerInfoSerde { pub struct PeerInfoSerde {
#[serde(default, deserialize_with = "deserialize_string")] #[serde(default, deserialize_with = "deserialize_string")]
@ -1124,6 +1156,17 @@ impl PeerConfig {
D: de::Deserializer<'de>, D: de::Deserializer<'de>,
{ {
let mut mp: HashMap<String, String> = de::Deserialize::deserialize(deserializer)?; let mut mp: HashMap<String, String> = de::Deserialize::deserialize(deserializer)?;
Self::insert_default_options(&mut mp);
Ok(mp)
}
fn default_options() -> HashMap<String, String> {
let mut mp: HashMap<String, String> = Default::default();
Self::insert_default_options(&mut mp);
return mp;
}
fn insert_default_options(mp: &mut HashMap<String, String>) {
let mut key = "codec-preference"; let mut key = "codec-preference";
if !mp.contains_key(key) { if !mp.contains_key(key) {
mp.insert(key.to_owned(), UserDefaultConfig::read().get(key)); mp.insert(key.to_owned(), UserDefaultConfig::read().get(key));
@ -1136,7 +1179,10 @@ impl PeerConfig {
if !mp.contains_key(key) { if !mp.contains_key(key) {
mp.insert(key.to_owned(), UserDefaultConfig::read().get(key)); mp.insert(key.to_owned(), UserDefaultConfig::read().get(key));
} }
Ok(mp) key = "touch-mode";
if !mp.contains_key(key) {
mp.insert(key.to_owned(), UserDefaultConfig::read().get(key));
}
} }
} }

View File

@ -1037,24 +1037,24 @@ fn serialize_resolutions(resolutions: &Vec<Resolution>) -> String {
} }
fn char_to_session_id(c: *const char) -> ResultType<SessionID> { fn char_to_session_id(c: *const char) -> ResultType<SessionID> {
if c.is_null() {
bail!("Session id ptr is null");
}
let cstr = unsafe { std::ffi::CStr::from_ptr(c as _) }; let cstr = unsafe { std::ffi::CStr::from_ptr(c as _) };
let str = cstr.to_str()?; let str = cstr.to_str()?;
SessionID::from_str(str).map_err(|e| anyhow!("{:?}", e)) SessionID::from_str(str).map_err(|e| anyhow!("{:?}", e))
} }
#[no_mangle] pub fn session_get_rgba_size(_session_id: SessionID) -> usize {
pub fn session_get_rgba_size(_session_uuid_str: *const char) -> usize {
#[cfg(not(feature = "flutter_texture_render"))] #[cfg(not(feature = "flutter_texture_render"))]
if let Ok(session_id) = char_to_session_id(_session_uuid_str) { if let Some(session) = SESSIONS.read().unwrap().get(&_session_id) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) { return session.rgba.read().unwrap().len();
return session.rgba.read().unwrap().len();
}
} }
0 0
} }
#[no_mangle] #[no_mangle]
pub fn session_get_rgba(session_uuid_str: *const char) -> *const u8 { pub extern "C" fn session_get_rgba(session_uuid_str: *const char) -> *const u8 {
if let Ok(session_id) = char_to_session_id(session_uuid_str) { if let Ok(session_id) = char_to_session_id(session_uuid_str) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) { if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
return session.get_rgba(); return session.get_rgba();
@ -1064,23 +1064,17 @@ pub fn session_get_rgba(session_uuid_str: *const char) -> *const u8 {
std::ptr::null() std::ptr::null()
} }
#[no_mangle] pub fn session_next_rgba(session_id: SessionID) {
pub fn session_next_rgba(session_uuid_str: *const char) { if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
if let Ok(session_id) = char_to_session_id(session_uuid_str) { return session.next_rgba();
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
return session.next_rgba();
}
} }
} }
#[inline] #[inline]
#[no_mangle] pub fn session_register_texture(_session_id: SessionID, _ptr: usize) {
pub fn session_register_texture(_session_uuid_str: *const char, _ptr: usize) {
#[cfg(feature = "flutter_texture_render")] #[cfg(feature = "flutter_texture_render")]
if let Ok(session_id) = char_to_session_id(_session_uuid_str) { if let Some(session) = SESSIONS.write().unwrap().get_mut(&_session_id) {
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) { return session.register_texture(_ptr);
return session.register_texture(_ptr);
}
} }
} }
@ -1211,9 +1205,6 @@ pub fn session_send_pointer(session_id: SessionID, msg: String) {
} }
} }
#[no_mangle]
unsafe extern "C" fn get_rgba() {}
/// Hooks for session. /// Hooks for session.
#[derive(Clone)] #[derive(Clone)]
pub enum SessionHook { pub enum SessionHook {

View File

@ -1388,18 +1388,6 @@ pub fn main_get_build_date() -> String {
crate::BUILD_DATE.to_string() crate::BUILD_DATE.to_string()
} }
#[no_mangle]
unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *const c_char {
let name = CStr::from_ptr(name);
let locale = CStr::from_ptr(locale);
let res = if let (Ok(name), Ok(locale)) = (name.to_str(), locale.to_str()) {
crate::client::translate_locale(name.to_owned(), locale)
} else {
String::new()
};
CString::from_vec_unchecked(res.into_bytes()).into_raw()
}
fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) { fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) {
let data = HashMap::from([ let data = HashMap::from([
("name", "callback_query_onlines".to_owned()), ("name", "callback_query_onlines".to_owned()),
@ -1412,6 +1400,22 @@ fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) {
); );
} }
pub fn translate(name: String, locale: String) -> SyncReturn<String> {
SyncReturn(crate::client::translate_locale(name, &locale))
}
pub fn session_get_rgba_size(session_id: SessionID) -> SyncReturn<usize> {
SyncReturn(super::flutter::session_get_rgba_size(session_id))
}
pub fn session_next_rgba(session_id: SessionID) -> SyncReturn<()> {
SyncReturn(super::flutter::session_next_rgba(session_id))
}
pub fn session_register_texture(session_id: SessionID, ptr: usize) -> SyncReturn<()> {
SyncReturn(super::flutter::session_register_texture(session_id, ptr))
}
pub fn query_onlines(ids: Vec<String>) { pub fn query_onlines(ids: Vec<String>) {
#[cfg(not(any(target_os = "ios")))] #[cfg(not(any(target_os = "ios")))]
crate::rendezvous_mediator::query_online_states(ids, handle_query_onlines) crate::rendezvous_mediator::query_online_states(ids, handle_query_onlines)