Initial commit: RustDesk 1.4.7 macOS desktop port
- Filled empty libs/hbb_common/ submodule (cloned from rustdesk/hbb_common) - Patched Flutter 3.44 / Dart 3.12 compatibility: * flutter/lib/generated_bridge.dart: asTypedList with cast<>, DartPort=Int64 * flutter/lib/common.dart: DialogTheme->DialogThemeData, TabBarTheme->TabBarThemeData * flutter/pubspec.yaml: extended_text 14.0.0->15.0.2, google_fonts override 5.0.0 * flutter/macos/Runner/Configs/Release.xcconfig: EXCLUDED_ARCHS=x86_64 - Build verified: cargo check + cargo build --features flutter + cargo build --release --features flutter - Verified flutter build macos --debug and --release both produce working .app - Verified .dmg installer (27MB arm64) created via hdiutil - Build deps: Xcode 26.5, CocoaPods 1.16.2, Flutter 3.44, VCPKG arm64-osx
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
import 'dart:js' as js;
|
||||
import 'dart:html' as html;
|
||||
// cycle imports, maybe we can improve this
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
|
||||
final isAndroid_ = false;
|
||||
final isIOS_ = false;
|
||||
final isWindows_ = false;
|
||||
final isMacOS_ = false;
|
||||
final isLinux_ = false;
|
||||
final isWeb_ = true;
|
||||
final isWebDesktop_ = !js.context.callMethod('isMobile');
|
||||
|
||||
final isDesktop_ = false;
|
||||
|
||||
String get screenInfo_ => js.context.callMethod('getByName', ['screen_info']);
|
||||
|
||||
final _localOs = js.context.callMethod('getByName', ['local_os', '']);
|
||||
final isWebOnWindows_ = _localOs == kPeerPlatformWindows;
|
||||
final isWebOnLinux_ = _localOs == kPeerPlatformLinux;
|
||||
final isWebOnMacOS_ = _localOs == kPeerPlatformMacOS;
|
||||
@@ -0,0 +1,127 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:js' as js;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/model.dart' as model;
|
||||
|
||||
class CursorData {
|
||||
final String key;
|
||||
final String url;
|
||||
final double hotX;
|
||||
final double hotY;
|
||||
final int width;
|
||||
final int height;
|
||||
|
||||
CursorData({
|
||||
required this.key,
|
||||
required this.url,
|
||||
required this.hotX,
|
||||
required this.hotY,
|
||||
required this.width,
|
||||
required this.height,
|
||||
});
|
||||
}
|
||||
|
||||
/// The cursor manager
|
||||
class CursorManager {
|
||||
final Map<String, CursorData> _cursors = <String, CursorData>{};
|
||||
String latestKey = '';
|
||||
|
||||
CursorManager._();
|
||||
static CursorManager instance = CursorManager._();
|
||||
|
||||
Future<void> registerCursor(CursorData data) async {
|
||||
_cursors[data.key] = data;
|
||||
}
|
||||
|
||||
Future<void> deleteCursor(String key) async {
|
||||
_cursors.remove(key);
|
||||
}
|
||||
|
||||
Future<void> setSystemCursor(String key) async {
|
||||
if (latestKey == key) {
|
||||
return;
|
||||
}
|
||||
latestKey = key;
|
||||
|
||||
final CursorData? cursorData = _cursors[key];
|
||||
if (cursorData != null) {
|
||||
js.context.callMethod('setByName', [
|
||||
'cursor',
|
||||
jsonEncode({
|
||||
'url': cursorData.url,
|
||||
'hotx': cursorData.hotX.toInt(),
|
||||
'hoty': cursorData.hotY.toInt(),
|
||||
})
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resetSystemCursor() async {
|
||||
latestKey = '';
|
||||
js.context.callMethod('setByName', ['cursor', 'auto']);
|
||||
}
|
||||
}
|
||||
|
||||
class FlutterCustomMemoryImageCursor extends MouseCursor {
|
||||
final String key;
|
||||
const FlutterCustomMemoryImageCursor({required this.key});
|
||||
|
||||
@override
|
||||
MouseCursorSession createSession(int device) =>
|
||||
_FlutterCustomMemoryImageCursorSession(this, device);
|
||||
|
||||
@override
|
||||
String get debugDescription =>
|
||||
objectRuntimeType(this, 'FlutterCustomMemoryImageCursor');
|
||||
}
|
||||
|
||||
class _FlutterCustomMemoryImageCursorSession extends MouseCursorSession {
|
||||
_FlutterCustomMemoryImageCursorSession(
|
||||
FlutterCustomMemoryImageCursor cursor, int device)
|
||||
: super(cursor, device);
|
||||
|
||||
@override
|
||||
FlutterCustomMemoryImageCursor get cursor =>
|
||||
super.cursor as FlutterCustomMemoryImageCursor;
|
||||
|
||||
@override
|
||||
Future<void> activate() async {
|
||||
await CursorManager.instance.setSystemCursor(cursor.key);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
deleteCustomCursor(String key) => CursorManager.instance.deleteCursor(key);
|
||||
resetSystemCursor() => CursorManager.instance.resetSystemCursor();
|
||||
|
||||
MouseCursor buildCursorOfCache(
|
||||
model.CursorModel cursor, double scale, model.CursorData? cache) {
|
||||
if (cache == null) {
|
||||
return MouseCursor.defer;
|
||||
} else {
|
||||
final key = cache.updateGetKey(scale);
|
||||
if (!cursor.cachedKeys.contains(key)) {
|
||||
// data should be checked here, because it may be changed after `updateGetKey()`
|
||||
final data = cache.data;
|
||||
if (data == null) {
|
||||
return MouseCursor.defer;
|
||||
}
|
||||
debugPrint(
|
||||
"Register custom cursor with key $key (${cache.hotx},${cache.hoty})");
|
||||
CursorManager.instance.registerCursor(CursorData(
|
||||
key: key,
|
||||
url: 'data:image/rgba;base64,${base64Encode(data)}',
|
||||
width: (cache.width * cache.scale).toInt(),
|
||||
height: (cache.height * cache.scale).toInt(),
|
||||
hotX: cache.hotx,
|
||||
hotY: cache.hoty));
|
||||
cursor.addKey(key);
|
||||
}
|
||||
return FlutterCustomMemoryImageCursor(key: key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
Future<void> webselectFiles({required bool is_folder}) async {
|
||||
throw UnimplementedError("webselectFiles");
|
||||
}
|
||||
|
||||
Future<void> webSendLocalFiles(
|
||||
{required int handleIndex,
|
||||
required int actId,
|
||||
required String path,
|
||||
required String to,
|
||||
required int fileNum,
|
||||
required bool includeHidden,
|
||||
required bool isRemote}) {
|
||||
throw UnimplementedError("webSendLocalFiles");
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
abstract class NativeHandler {
|
||||
bool onEvent(Map<String, dynamic> evt);
|
||||
}
|
||||
|
||||
class NativeUiHandler extends NativeHandler {
|
||||
NativeUiHandler._();
|
||||
|
||||
static NativeUiHandler instance = NativeUiHandler._();
|
||||
|
||||
@override
|
||||
bool onEvent(Map<String, dynamic> evt) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
||||
|
||||
class WebSettingsPage extends StatelessWidget {
|
||||
const WebSettingsPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _buildDesktopButton(context);
|
||||
}
|
||||
|
||||
Widget _buildDesktopButton(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) =>
|
||||
DesktopSettingPage(initialTabkey: SettingsTabKey.general),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class TextureRgbaRenderer {
|
||||
Future<int> createTexture(int key) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Future<bool> closeTexture(int key) {
|
||||
return Future(() => true);
|
||||
}
|
||||
|
||||
Future<bool> onRgba(
|
||||
int key, Uint8List data, int height, int width, int strideAlign) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Future<int> getTexturePtr(int key) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:js' as js;
|
||||
|
||||
Future<void> webselectFiles({required bool is_folder}) async {
|
||||
return Future(
|
||||
() => js.context.callMethod('setByName', ['select_files', is_folder]));
|
||||
}
|
||||
|
||||
Future<void> webSendLocalFiles(
|
||||
{required int handleIndex,
|
||||
required int actId,
|
||||
required String path,
|
||||
required String to,
|
||||
required int fileNum,
|
||||
required bool includeHidden,
|
||||
required bool isRemote}) {
|
||||
return Future(() => js.context.callMethod('setByName', [
|
||||
'send_local_files',
|
||||
jsonEncode({
|
||||
'id': actId,
|
||||
'handle_index': handleIndex,
|
||||
'path': path,
|
||||
'to': to,
|
||||
'file_num': fileNum,
|
||||
'include_hidden': includeHidden,
|
||||
'is_remote': isRemote,
|
||||
})
|
||||
]));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
/// No use, for compilation only.
|
||||
int getWindowsTargetBuildNumber_() {
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user