Merge branch 'ios_1.2.2'
This commit is contained in:
commit
5b802e9edd
11
.github/workflows/flutter-build.yml
vendored
11
.github/workflows/flutter-build.yml
vendored
@ -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'
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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";
|
||||||
|
@ -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("", "");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
#import "GeneratedPluginRegistrant.h"
|
#import "GeneratedPluginRegistrant.h"
|
||||||
|
|
||||||
#import "ffi.h"
|
#import "bridge_generated.h"
|
||||||
|
@ -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*);
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
277
flutter/lib/common/widgets/setting_widgets.dart
Normal file
277
flutter/lib/common/widgets/setting_widgets.dart
Normal 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))
|
||||||
|
];
|
||||||
|
}
|
@ -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,74 +995,19 @@ 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(
|
children: [
|
||||||
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(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Obx(() => _LabeledTextField(context, 'ID Server', idController,
|
Obx(() => _LabeledTextField(context, 'ID Server', idController,
|
||||||
idErrMsg.value, enabled, secure)),
|
idErrMsg.value, enabled, secure)),
|
||||||
Obx(() => _LabeledTextField(context, 'Relay Server',
|
Obx(() => _LabeledTextField(context, 'Relay Server',
|
||||||
relayController, relayErrMsg.value, enabled, secure)),
|
relayController, relayErrMsg.value, enabled, secure)),
|
||||||
Obx(() => _LabeledTextField(context, 'API Server', apiController,
|
Obx(() => _LabeledTextField(context, 'API Server',
|
||||||
apiErrMsg.value, enabled, secure)),
|
apiController, apiErrMsg.value, enabled, secure)),
|
||||||
_LabeledTextField(
|
_LabeledTextField(
|
||||||
context, 'Key', keyController, '', enabled, secure),
|
context, 'Key', keyController, '', enabled, secure),
|
||||||
Row(
|
Row(
|
||||||
@ -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),
|
|
||||||
))
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -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 [
|
||||||
|
@ -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(
|
PopupMenuItem(
|
||||||
value: "login",
|
value: "login",
|
||||||
child: Text(gFFI.userModel.userName.value.isEmpty
|
child: Text(gFFI.userModel.userName.value.isEmpty
|
||||||
? translate("Login")
|
? translate("Login")
|
||||||
: '${translate("Logout")} (${gFFI.userModel.userName.value})'),
|
: '${translate("Logout")} (${gFFI.userModel.userName.value})'),
|
||||||
)
|
)
|
||||||
]) +
|
] +
|
||||||
[
|
[
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: "about",
|
value: "about",
|
||||||
|
@ -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);
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,11 +424,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
|
if (isAndroid)
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
title: Text(translate("Recording")),
|
title: Text(translate("Recording")),
|
||||||
tiles: [
|
tiles: [
|
||||||
SettingsTile.switchTile(
|
SettingsTile.switchTile(
|
||||||
title: Text(translate('Automatically record incoming sessions')),
|
title:
|
||||||
|
Text(translate('Automatically record incoming sessions')),
|
||||||
leading: Icon(Icons.videocam),
|
leading: Icon(Icons.videocam),
|
||||||
description: FutureBuilder(
|
description: FutureBuilder(
|
||||||
builder: (ctx, data) => Offstage(
|
builder: (ctx, data) => Offstage(
|
||||||
@ -451,10 +453,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (isAndroid)
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
title: Text(translate("Share Screen")),
|
title: Text(translate("Share Screen")),
|
||||||
tiles: shareScreenTiles,
|
tiles: shareScreenTiles,
|
||||||
),
|
),
|
||||||
|
defaultDisplaySection(),
|
||||||
|
if (isAndroid)
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
title: Text(translate("Enhancements")),
|
title: Text(translate("Enhancements")),
|
||||||
tiles: enhancementsTiles,
|
tiles: enhancementsTiles,
|
||||||
@ -462,7 +467,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
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,14 +482,15 @@ 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)
|
||||||
|
SettingsTile(
|
||||||
onPressed: (context) => onCopyFingerprint(_fingerprint),
|
onPressed: (context) => onCopyFingerprint(_fingerprint),
|
||||||
title: Text(translate("Fingerprint")),
|
title: Text(translate("Fingerprint")),
|
||||||
value: Padding(
|
value: Padding(
|
||||||
@ -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))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -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),
|
||||||
)
|
)
|
||||||
] +
|
] +
|
||||||
(isAndroid
|
[
|
||||||
? [
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: relayCtrl,
|
controller: relayCtrl,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: translate('Relay Server'),
|
labelText: translate('Relay Server'),
|
||||||
errorText: relayServerMsg),
|
errorText: relayServerMsg.value.isEmpty
|
||||||
|
? null
|
||||||
|
: relayServerMsg.value),
|
||||||
)
|
)
|
||||||
]
|
] +
|
||||||
: []) +
|
|
||||||
[
|
[
|
||||||
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;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,24 +1064,18 @@ 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 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.next_rgba();
|
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);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user