Merge remote-tracking branch 'rustdesk/master' into flutter_desktop

# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	build.rs
#	flutter/.gitignore
#	flutter/lib/common.dart
#	flutter/lib/mobile/pages/remote_page.dart
#	flutter/lib/models/model.dart
#	flutter/lib/models/native_model.dart
#	flutter/lib/models/server_model.dart
#	flutter/pubspec.lock
#	flutter/pubspec.yaml
#	src/client.rs
#	src/client/file_trait.rs
#	src/flutter.rs
#	src/mobile_ffi.rs
#	src/ui.rs
This commit is contained in:
Kingtous
2022-06-27 11:18:53 +08:00
138 changed files with 5534 additions and 798 deletions

5
flutter/.gitignore vendored
View File

@@ -54,4 +54,7 @@ linux/flutter/generated_plugins.cmake
macos/Flutter/GeneratedPluginRegistrant.swift
windows/flutter/generated_plugin_registrant.cc
windows/flutter/generated_plugin_registrant.h
windows/flutter/generated_plugins.cmake
windows/flutter/generated_plugins.cmake
flutter_export_environment.sh
Flutter-Generated.xcconfig
key.jks

View File

@@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!--<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />-->
<application

View File

@@ -1,5 +1,11 @@
package com.carriez.flutter_hbb
/**
* Handle remote input and dispatch android gesture
*
* Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG
*/
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.content.Context
@@ -32,12 +38,6 @@ class InputService : AccessibilityService() {
get() = ctx != null
}
private external fun init(ctx: Context)
init {
System.loadLibrary("rustdesk")
}
private val logTag = "input service"
private var leftIsDown = false
private var touchPath = Path()
@@ -50,9 +50,8 @@ class InputService : AccessibilityService() {
private val wheelActionsQueue = LinkedList<GestureDescription>()
private var isWheelActionsPolling = false
@Keep
@RequiresApi(Build.VERSION_CODES.N)
fun rustMouseInput(mask: Int, _x: Int, _y: Int) {
fun onMouseInput(mask: Int, _x: Int, _y: Int) {
val x = if (_x < 0) {
0
} else {
@@ -207,12 +206,15 @@ class InputService : AccessibilityService() {
}
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onServiceConnected() {
super.onServiceConnected()
ctx = this
Log.d(logTag, "onServiceConnected!")
init(this)
}
override fun onDestroy() {
ctx = null
super.onDestroy()
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {}

View File

@@ -1,5 +1,12 @@
package com.carriez.flutter_hbb
/**
* Handle events from flutter
* Request MediaProjection permission
*
* Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG
*/
import android.app.Activity
import android.content.ComponentName
import android.content.Context

View File

@@ -1,8 +1,11 @@
package com.carriez.flutter_hbb
/**
* Capture screen,get video and audio,send to rust.
* Handle notification
* Dispatch notifications
*
* Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG
*/
package com.carriez.flutter_hbb
import android.Manifest
import android.annotation.SuppressLint
@@ -69,10 +72,32 @@ class MainService : Service() {
System.loadLibrary("rustdesk")
}
@Keep
@RequiresApi(Build.VERSION_CODES.N)
fun rustMouseInput(mask: Int, x: Int, y: Int) {
// turn on screen with LIFT_DOWN when screen off
if (!powerManager.isInteractive && mask == LIFT_DOWN) {
if (wakeLock.isHeld) {
Log.d(logTag,"Turn on Screen, WakeLock release")
wakeLock.release()
}
Log.d(logTag,"Turn on Screen")
wakeLock.acquire(5000)
} else {
InputService.ctx?.onMouseInput(mask,x,y)
}
}
@Keep
fun rustGetByName(name: String): String {
return when (name) {
"screen_size" -> "${SCREEN_INFO.width}:${SCREEN_INFO.height}"
"screen_size" -> {
JSONObject().apply {
put("width",SCREEN_INFO.width)
put("height",SCREEN_INFO.height)
put("scale",SCREEN_INFO.scale)
}.toString()
}
else -> ""
}
}
@@ -130,6 +155,9 @@ class MainService : Service() {
private var serviceLooper: Looper? = null
private var serviceHandler: Handler? = null
private val powerManager: PowerManager by lazy { applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager }
private val wakeLock: PowerManager.WakeLock by lazy { powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "rustdesk:wakelock")}
// jvm call rust
private external fun init(ctx: Context)
private external fun startServer()
@@ -191,10 +219,6 @@ class MainService : Service() {
}
override fun onDestroy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
InputService.ctx?.disableSelf()
}
InputService.ctx = null
checkMediaPermission()
super.onDestroy()
}
@@ -383,10 +407,6 @@ class MainService : Service() {
mediaProjection = null
checkMediaPermission()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
InputService.ctx?.disableSelf()
}
InputService.ctx = null
stopForeground(true)
stopSelf()
}

View File

@@ -1,7 +1,8 @@
#!/usr/bin/env bash
$ANDROID_NDK/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-strip android/app/src/main/jniLibs/arm64-v8a/*
flutter build apk --target-platform android-arm64 --release --obfuscate --split-debug-info ./split-debug-info
flutter build appbundle --target-platform android-arm64 --release --obfuscate --split-debug-info ./split-debug-info
flutter build apk --target-platform android-arm64,android-arm --release --obfuscate --split-debug-info ./split-debug-info
flutter build apk ---split-per-abi --target-platform android-arm64,android-arm --release --obfuscate --split-debug-info ./split-debug-info
flutter build appbundle --target-platform android-arm64,android-arm --release --obfuscate --split-debug-info ./split-debug-info
# build in linux
# $ANDROID_NDK/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip android/app/src/main/jniLibs/arm64-v8a/*

125
flutter/build_android_deps.sh Executable file
View File

@@ -0,0 +1,125 @@
#!/bin/bash
# Build libyuv / opus / libvpx / oboe for Android
# Required:
# 1. set VCPKG_ROOT / ANDROID_NDK path environment variables
# 2. vcpkg initialized
# 3. ndk, version: 22 (if ndk < 22 you need to change LD as `export LD=$TOOLCHAIN/bin/$NDK_LLVM_TARGET-ld`)
if [ -z "$ANDROID_NDK" ]; then
echo "Failed! Please set ANDROID_NDK"
exit 1
fi
if [ -z "$VCPKG_ROOT" ]; then
echo "Failed! Please set VCPKG_ROOT"
exit 1
fi
API_LEVEL="21"
# NDK llvm toolchain
HOST_TAG="linux-x86_64" # current platform, set as `ls $ANDROID_NDK/toolchains/llvm/prebuilt/`
TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG
function build {
ANDROID_ABI=$1
VCPKG_TARGET=$2
NDK_LLVM_TARGET=$3
LIBVPX_TARGET=$4
PREFIX=$VCPKG_ROOT/installed/$VCPKG_TARGET/
# 1
echo "*** [$ANDROID_ABI][Start] Build opus / libyuv from vcpkg"
export ANDROID_NDK_HOME=$ANDROID_NDK
pushd $VCPKG_ROOT
$VCPKG_ROOT/vcpkg install opus --triplet $VCPKG_TARGET
$VCPKG_ROOT/vcpkg install libyuv --triplet $VCPKG_TARGET
popd
echo "*** [$ANDROID_ABI][Finished] Build opus / libyuv from vcpkg"
# 2
echo "*** [$ANDROID_ABI][Start] Build libvpx"
pushd build/libvpx
export AR=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ar
export AS=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-as
export LD=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ld.gold # if ndk < 22, use aarch64-linux-android-ld
export RANLIB=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ranlib
export STRIP=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-strip
if [ $NDK_LLVM_TARGET == "arm-linux-androideabi" ]
then
export CC=$TOOLCHAIN/bin/armv7a-linux-androideabi${API_LEVEL}-clang
export CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi${API_LEVEL}-clang++
else
export CC=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}${API_LEVEL}-clang
export CXX=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}${API_LEVEL}-clang++
fi
make clean
./configure --target=$LIBVPX_TARGET \
--enable-pic --disable-vp8 \
--disable-webm-io \
--disable-unit-tests \
--disable-examples \
--disable-libyuv \
--disable-postproc \
--disable-vp8 \
--disable-tools \
--disable-docs \
--prefix=$PREFIX
make -j5
make install
popd
echo "*** [$ANDROID_ABI][Finished] Build libvpx"
# 3
echo "*** [$ANDROID_ABI][Start] Build oboe"
pushd build/oboe
make clean
cmake -DBUILD_SHARED_LIBS=true \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DANDROID_TOOLCHAIN=clang \
-DANDROID_STL=c++_shared \
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DCMAKE_INSTALL_PREFIX=$PREFIX \
-DANDROID_ABI=$ANDROID_ABI \
-DANDROID_PLATFORM=android-$API_LEVEL
make -j5
make install
mv $PREFIX/lib/$ANDROID_ABI/liboboe.a $PREFIX/lib/
popd
echo "*** [$ANDROID_ABI][Finished] Build oboe"
echo "*** [$ANDROID_ABI][All Finished]"
}
git clone -b v1.11.0 --depth=1 https://github.com/webmproject/libvpx.git build/libvpx
git clone -b 1.6.1 --depth=1 https://github.com/google/oboe build/oboe
patch -N -d build/oboe -p1 < ../src/oboe.patch
# VCPKG_TARGET ANDROID_ABI
# arm64-android arm64-v8a
# arm-android armeabi-v7a
# x64-android x86_64
# x86-android x86
# NDK_LLVM_TARGET
# aarch64-linux-android
# arm-linux-androideabi
# x86_64-linux-android
# i686-linux-android
# LIBVPX_TARGET :
# arm64-android-gcc
# armv7-android-gcc
# x86_64-android-gcc
# x86-android-gcc
# args: ANDROID_ABI VCPKG_TARGET NDK_LLVM_TARGET LIBVPX_TARGET
build arm64-v8a arm64-android aarch64-linux-android arm64-android-gcc
build armeabi-v7a arm-android arm-linux-androideabi armv7-android-gcc
# rm -rf build/libvpx
# rm -rf build/oboe

View File

@@ -236,7 +236,7 @@ class AccessibilityListener extends StatelessWidget {
}
},
onPointerUp: (evt) {
if (evt.size == 1 && GestureBinding.instance != null) {
if (evt.size == 1) {
GestureBinding.instance.handlePointerEvent(PointerUpEvent(
pointer: evt.pointer + offset,
size: 0.1,
@@ -246,7 +246,7 @@ class AccessibilityListener extends StatelessWidget {
}
},
onPointerMove: (evt) {
if (evt.size == 1 && GestureBinding.instance != null) {
if (evt.size == 1) {
GestureBinding.instance.handlePointerEvent(PointerMoveEvent(
pointer: evt.pointer + offset,
size: 0.1,

View File

@@ -113,8 +113,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
: InkWell(
onTap: () async {
final url = _updateUrl + '.apk';
if (await canLaunch(url)) {
await launch(url);
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
}
},
child: Container(
@@ -275,7 +275,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
? []
: [
PopupMenuItem<String>(
child: Text(translate('File transfer')), value: 'file')
child: Text(translate('Transfer File')), value: 'file')
]),
elevation: 8,
);

View File

@@ -35,6 +35,7 @@ class _RemotePageState extends State<RemotePage> {
String _value = '';
double _scale = 1;
double _mouseScrollIntegral = 0; // mouse scroll speed controller
Orientation? _currentOrientation;
var _more = true;
var _fn = false;
@@ -127,7 +128,7 @@ class _RemotePageState extends State<RemotePage> {
common < oldValue.length &&
common < newValue.length &&
newValue[common] == oldValue[common];
++common);
++common) {}
for (i = 0; i < oldValue.length - common; ++i) {
gFFI.inputKey('VK_BACK');
}
@@ -260,12 +261,22 @@ class _RemotePageState extends State<RemotePage> {
color: Colors.black,
child: isWebDesktop
? getBodyForDesktopWithListener(keyboard)
: SafeArea(
child: Container(
: SafeArea(child:
OrientationBuilder(builder: (ctx, orientation) {
if (_currentOrientation != orientation) {
debugPrint("on orientation changed");
Timer(Duration(milliseconds: 200), () {
resetMobileActionsOverlay();
_currentOrientation = orientation;
FFI.canvasModel.updateViewStyle();
});
}
return Container(
color: MyTheme.canvasColor,
child: _isPhysicalMouse
? getBodyForMobile()
: getBodyForMobileWithGesture())));
: getBodyForMobileWithGesture());
})));
})
],
))),

View File

@@ -204,7 +204,7 @@ class _PermissionCheckerState extends State<PermissionChecker> {
serverModel.toggleService),
PermissionRow(translate("Input Control"), serverModel.inputOk,
serverModel.toggleInput),
PermissionRow(translate("File Transfer"), serverModel.fileOk,
PermissionRow(translate("Transfer File"), serverModel.fileOk,
serverModel.toggleFile),
hasAudioPermission
? PermissionRow(translate("Audio Capture"), serverModel.audioOk,

View File

@@ -70,8 +70,8 @@ class _SettingsState extends State<SettingsPage> {
tiles: [
SettingsTile.navigation(
onPressed: (context) async {
if (await canLaunch(url)) {
await launch(url);
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
}
},
title: Text(translate("Version: ") + version),
@@ -107,8 +107,8 @@ void showAbout() {
InkWell(
onTap: () async {
const url = 'https://rustdesk.com/';
if (await canLaunch(url)) {
await launch(url);
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
}
},
child: Padding(
@@ -151,7 +151,7 @@ fetch('http://localhost:21114/api/login', {
'uuid': gFFI.getByName('uuid')
};
try {
final response = await http.post(Uri.parse('${url}/api/login'),
final response = await http.post(Uri.parse('$url/api/login'),
headers: {"Content-Type": "application/json"}, body: json.encode(body));
return parseResp(response.body);
} catch (e) {
@@ -189,7 +189,7 @@ void refreshCurrentUser() async {
'uuid': gFFI.getByName('uuid')
};
try {
final response = await http.post(Uri.parse('${url}/api/currentUser'),
final response = await http.post(Uri.parse('$url/api/currentUser'),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer $token"
@@ -215,7 +215,7 @@ void logout() async {
'uuid': gFFI.getByName('uuid')
};
try {
await http.post(Uri.parse('${url}/api/logout'),
await http.post(Uri.parse('$url/api/logout'),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer $token"
@@ -245,7 +245,7 @@ String getUrl() {
url = 'http://${tmp[0]}:$port';
}
} else {
url = 'http://${url}:21114';
url = 'http://$url:21114';
}
}
}

View File

@@ -594,10 +594,7 @@ class _TapTracker {
required this.entry,
required Duration doubleTapMinTime,
required this.gestureSettings,
}) : assert(doubleTapMinTime != null),
assert(event != null),
assert(event.buttons != null),
pointer = event.pointer,
}) : pointer = event.pointer,
_initialGlobalPosition = event.position,
initialButtons = event.buttons,
_doubleTapMinTimeCountdown =
@@ -643,7 +640,7 @@ class _TapTracker {
/// CountdownZoned tracks whether the specified duration has elapsed since
/// creation, honoring [Zone].
class _CountdownZoned {
_CountdownZoned({required Duration duration}) : assert(duration != null) {
_CountdownZoned({required Duration duration}) {
Timer(duration, _onTimeout);
}

View File

@@ -228,6 +228,12 @@ class DraggableMobileActions extends StatelessWidget {
}
}
resetMobileActionsOverlay() {
if (mobileActionsOverlayEntry == null) return;
hideMobileActionsOverlay();
showMobileActionsOverlay();
}
showMobileActionsOverlay() {
if (mobileActionsOverlayEntry != null) return;
if (globalKey.currentContext == null ||

View File

@@ -621,15 +621,7 @@ class FileFetcher {
tryCompleteTask(String? msg, String? isLocalStr) {
if (msg == null || isLocalStr == null) return;
late final isLocal;
late final tasks;
if (isLocalStr == "true") {
isLocal = true;
} else if (isLocalStr == "false") {
isLocal = false;
} else {
return;
}
try {
final fd = FileDirectory.fromJson(jsonDecode(msg));
if (fd.id > 0) {

View File

@@ -229,6 +229,7 @@ class FfiModel with ChangeNotifier {
}
void handleSwitchDisplay(Map<String, dynamic> evt) {
final oldOrientation = _display.width > _display.height;
var old = _pi.currentDisplay;
_pi.currentDisplay = int.parse(evt['display']);
_display.x = double.parse(evt['x']);
@@ -237,6 +238,11 @@ class FfiModel with ChangeNotifier {
_display.height = int.parse(evt['height']);
if (old != _pi.currentDisplay)
parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y);
// remote is mobile, and orientation changed
if ((_display.width > _display.height) != oldOrientation) {
gFFI.canvasModel.updateViewStyle();
}
notifyListeners();
}

View File

@@ -56,7 +56,6 @@ class ServerModel with ChangeNotifier {
* 2. check config
* audio true by default (if permission on) (false default < Android 10)
* file true by default (if permission on)
* input false by default (it need turning on manually everytime)
*/
await Future.delayed(Duration(seconds: 1));

View File

@@ -1,3 +1,5 @@
// ignore_for_file: avoid_web_libraries_in_flutter
import 'dart:convert';
import 'dart:typed_data';
import 'dart:js';

2
flutter/ndk_arm.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
cargo ndk --platform 21 --target armv7-linux-androideabi build --release

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
# 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
version: 1.1.10+27
version: 1.1.10-1+28
environment:
sdk: ">=2.16.1"