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:
xuwenwei
2026-06-08 21:42:20 +08:00
parent 3d8db09123
commit d3b6026bfd
840 changed files with 291780 additions and 1 deletions
File diff suppressed because it is too large Load Diff
+21
View File
@@ -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;
+127
View File
@@ -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);
}
}
+14
View File
@@ -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");
}
+14
View File
@@ -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();
}
}
+26
View File
@@ -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();
}
}
+30
View File
@@ -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,
})
]));
}
+5
View File
@@ -0,0 +1,5 @@
/// No use, for compilation only.
int getWindowsTargetBuildNumber_() {
return 0;
}