mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-05-11 18:36:11 +02:00
Refact. Flutter web desktop (#7539)
* Refact. Flutter web desktop Signed-off-by: fufesou <shuanglongchen@yeah.net> * Flutter web, prevent default context menu Signed-off-by: fufesou <shuanglongchen@yeah.net> --------- Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
810b980e6b
commit
6e44a91d0b
27 changed files with 362 additions and 153 deletions
|
@ -29,6 +29,8 @@ import '../consts.dart';
|
|||
import 'common/widgets/overlay.dart';
|
||||
import 'mobile/pages/file_manager_page.dart';
|
||||
import 'mobile/pages/remote_page.dart';
|
||||
import 'desktop/pages/remote_page.dart' as desktop_remote;
|
||||
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
|
||||
import 'models/input_model.dart';
|
||||
import 'models/model.dart';
|
||||
import 'models/platform_model.dart';
|
||||
|
@ -48,7 +50,7 @@ final isMacOS = isMacOS_;
|
|||
final isLinux = isLinux_;
|
||||
final isDesktop = isDesktop_;
|
||||
final isWeb = isWeb_;
|
||||
var isWebDesktop = false;
|
||||
final isWebDesktop = isWebDesktop_;
|
||||
var isMobile = isAndroid || isIOS;
|
||||
var version = '';
|
||||
int androidVersion = 0;
|
||||
|
@ -60,6 +62,8 @@ DesktopType? desktopType;
|
|||
bool get isMainDesktopWindow =>
|
||||
desktopType == DesktopType.main || desktopType == DesktopType.cm;
|
||||
|
||||
String get screenInfo => screenInfo_;
|
||||
|
||||
/// Check if the app is running with single view mode.
|
||||
bool isSingleViewApp() {
|
||||
return desktopType == DesktopType.cm;
|
||||
|
@ -233,11 +237,13 @@ class MyTheme {
|
|||
);
|
||||
|
||||
static SwitchThemeData switchTheme() {
|
||||
return SwitchThemeData(splashRadius: isDesktop ? 0 : kRadialReactionRadius);
|
||||
return SwitchThemeData(
|
||||
splashRadius: (isDesktop || isWebDesktop) ? 0 : kRadialReactionRadius);
|
||||
}
|
||||
|
||||
static RadioThemeData radioTheme() {
|
||||
return RadioThemeData(splashRadius: isDesktop ? 0 : kRadialReactionRadius);
|
||||
return RadioThemeData(
|
||||
splashRadius: (isDesktop || isWebDesktop) ? 0 : kRadialReactionRadius);
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
|
@ -286,7 +292,7 @@ class MyTheme {
|
|||
static EdgeInsets dialogContentPadding({bool actions = true}) {
|
||||
final double p = dialogPadding;
|
||||
|
||||
return isDesktop
|
||||
return (isDesktop || isWebDesktop)
|
||||
? EdgeInsets.fromLTRB(p, p, p, actions ? (p - 4) : p)
|
||||
: EdgeInsets.fromLTRB(p, p, p, actions ? (p / 2) : p);
|
||||
}
|
||||
|
@ -294,12 +300,12 @@ class MyTheme {
|
|||
static EdgeInsets dialogActionsPadding() {
|
||||
final double p = dialogPadding;
|
||||
|
||||
return isDesktop
|
||||
return (isDesktop || isWebDesktop)
|
||||
? EdgeInsets.fromLTRB(p, 0, p, (p - 4))
|
||||
: EdgeInsets.fromLTRB(p, 0, (p - mobileTextButtonPaddingLR), (p / 2));
|
||||
}
|
||||
|
||||
static EdgeInsets dialogButtonPadding = isDesktop
|
||||
static EdgeInsets dialogButtonPadding = (isDesktop || isWebDesktop)
|
||||
? EdgeInsets.only(left: dialogPadding)
|
||||
: EdgeInsets.only(left: dialogPadding / 3);
|
||||
|
||||
|
@ -371,10 +377,10 @@ class MyTheme {
|
|||
labelColor: Colors.black87,
|
||||
),
|
||||
tooltipTheme: tooltipTheme(),
|
||||
splashColor: isDesktop ? Colors.transparent : null,
|
||||
highlightColor: isDesktop ? Colors.transparent : null,
|
||||
splashFactory: isDesktop ? NoSplash.splashFactory : null,
|
||||
textButtonTheme: isDesktop
|
||||
splashColor: (isDesktop || isWebDesktop) ? Colors.transparent : null,
|
||||
highlightColor: (isDesktop || isWebDesktop) ? Colors.transparent : null,
|
||||
splashFactory: (isDesktop || isWebDesktop) ? NoSplash.splashFactory : null,
|
||||
textButtonTheme: (isDesktop || isWebDesktop)
|
||||
? TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
|
@ -414,7 +420,9 @@ class MyTheme {
|
|||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: isDesktop ? Color(0xFFECECEC) : Colors.transparent),
|
||||
color: (isDesktop || isWebDesktop)
|
||||
? Color(0xFFECECEC)
|
||||
: Colors.transparent),
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.0)),
|
||||
)),
|
||||
).copyWith(
|
||||
|
@ -440,7 +448,7 @@ class MyTheme {
|
|||
),
|
||||
),
|
||||
scrollbarTheme: scrollbarThemeDark,
|
||||
inputDecorationTheme: isDesktop
|
||||
inputDecorationTheme: (isDesktop || isWebDesktop)
|
||||
? InputDecorationTheme(
|
||||
fillColor: Color(0xFF24252B),
|
||||
filled: true,
|
||||
|
@ -467,10 +475,10 @@ class MyTheme {
|
|||
labelColor: Colors.white70,
|
||||
),
|
||||
tooltipTheme: tooltipTheme(),
|
||||
splashColor: isDesktop ? Colors.transparent : null,
|
||||
highlightColor: isDesktop ? Colors.transparent : null,
|
||||
splashFactory: isDesktop ? NoSplash.splashFactory : null,
|
||||
textButtonTheme: isDesktop
|
||||
splashColor: (isDesktop || isWebDesktop) ? Colors.transparent : null,
|
||||
highlightColor: (isDesktop || isWebDesktop) ? Colors.transparent : null,
|
||||
splashFactory: (isDesktop || isWebDesktop) ? NoSplash.splashFactory : null,
|
||||
textButtonTheme: (isDesktop || isWebDesktop)
|
||||
? TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
|
@ -818,7 +826,7 @@ class OverlayDialogManager {
|
|||
Offstage(
|
||||
offstage: !showCancel,
|
||||
child: Center(
|
||||
child: isDesktop
|
||||
child: (isDesktop || isWebDesktop)
|
||||
? dialogButton('Cancel', onPressed: cancel)
|
||||
: TextButton(
|
||||
style: flatButtonStyle,
|
||||
|
@ -1293,7 +1301,7 @@ class AndroidPermissionManager {
|
|||
}
|
||||
|
||||
static Future<bool> check(String type) {
|
||||
if (isDesktop) {
|
||||
if (isDesktop || isWeb) {
|
||||
return Future.value(true);
|
||||
}
|
||||
return gFFI.invokeMethod("check_permission", type);
|
||||
|
@ -1307,7 +1315,7 @@ class AndroidPermissionManager {
|
|||
/// We use XXPermissions to request permissions,
|
||||
/// for supported types, see https://github.com/getActivity/XXPermissions/blob/e46caea32a64ad7819df62d448fb1c825481cd28/library/src/main/java/com/hjq/permissions/Permission.java
|
||||
static Future<bool> request(String type) {
|
||||
if (isDesktop) {
|
||||
if (isDesktop || isWeb) {
|
||||
return Future.value(true);
|
||||
}
|
||||
|
||||
|
@ -2197,13 +2205,29 @@ connect(BuildContext context, String id,
|
|||
),
|
||||
);
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => RemotePage(
|
||||
id: id, password: password, isSharedPassword: isSharedPassword),
|
||||
),
|
||||
);
|
||||
if (isWebDesktop) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => desktop_remote.RemotePage(
|
||||
key: ValueKey(id),
|
||||
id: id,
|
||||
toolbarState: ToolbarState(),
|
||||
password: password,
|
||||
forceRelay: forceRelay,
|
||||
isSharedPassword: isSharedPassword,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => RemotePage(
|
||||
id: id, password: password, isSharedPassword: isSharedPassword),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2398,7 +2422,7 @@ Widget dialogButton(String text,
|
|||
Widget? icon,
|
||||
TextStyle? style,
|
||||
ButtonStyle? buttonStyle}) {
|
||||
if (isDesktop) {
|
||||
if (isDesktop || isWebDesktop) {
|
||||
if (isOutline) {
|
||||
return icon == null
|
||||
? OutlinedButton(
|
||||
|
|
|
@ -63,7 +63,7 @@ class _AddressBookState extends State<AddressBook> {
|
|||
retry: null, // remove retry
|
||||
close: () => gFFI.abModel.currentAbPushError.value = ''),
|
||||
Expanded(
|
||||
child: isDesktop
|
||||
child: (isDesktop || isWebDesktop)
|
||||
? _buildAddressBookDesktop()
|
||||
: _buildAddressBookMobile())
|
||||
],
|
||||
|
@ -311,7 +311,7 @@ class _AddressBookState extends State<AddressBook> {
|
|||
return tagBuilder(e);
|
||||
});
|
||||
final maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
|
||||
return isDesktop
|
||||
return (isDesktop || isWebDesktop)
|
||||
? gridView
|
||||
: LimitedBox(maxHeight: maxHeight, child: gridView);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -81,7 +80,7 @@ void changeIdDialog() {
|
|||
final Iterable violations = rules.where((r) => !r.validate(newId));
|
||||
if (violations.isNotEmpty) {
|
||||
setState(() {
|
||||
msg = isDesktop
|
||||
msg = (isDesktop || isWebDesktop)
|
||||
? '${translate('Prompt')}: ${violations.map((r) => r.name).join(', ')}'
|
||||
: violations.map((r) => r.name).join(', ');
|
||||
});
|
||||
|
@ -106,7 +105,7 @@ void changeIdDialog() {
|
|||
}
|
||||
setState(() {
|
||||
isInProgress = false;
|
||||
msg = isDesktop
|
||||
msg = (isDesktop || isWebDesktop)
|
||||
? '${translate('Prompt')}: ${translate(status)}'
|
||||
: translate(status);
|
||||
});
|
||||
|
@ -143,7 +142,7 @@ void changeIdDialog() {
|
|||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
isDesktop
|
||||
(isDesktop || isWebDesktop)
|
||||
? Obx(() => Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 4,
|
||||
|
@ -1109,7 +1108,7 @@ void showRequestElevationDialog(
|
|||
errorText: errPwd.isEmpty ? null : errPwd.value,
|
||||
),
|
||||
],
|
||||
).marginOnly(left: isDesktop ? 35 : 0),
|
||||
).marginOnly(left: (isDesktop || isWebDesktop) ? 35 : 0),
|
||||
).marginOnly(top: 10),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -47,7 +47,10 @@ class _MyGroupState extends State<MyGroup> {
|
|||
err: gFFI.groupModel.groupLoadError,
|
||||
retry: null,
|
||||
close: () => gFFI.groupModel.groupLoadError.value = ''),
|
||||
Expanded(child: isDesktop ? _buildDesktop() : _buildMobile())
|
||||
Expanded(
|
||||
child: (isDesktop || isWebDesktop)
|
||||
? _buildDesktop()
|
||||
: _buildMobile())
|
||||
],
|
||||
);
|
||||
});
|
||||
|
@ -164,7 +167,7 @@ class _MyGroupState extends State<MyGroup> {
|
|||
itemCount: items.length,
|
||||
itemBuilder: (context, index) => _buildUserItem(items[index]));
|
||||
var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
|
||||
return isDesktop
|
||||
return (isDesktop || isWebDesktop)
|
||||
? listView
|
||||
: LimitedBox(maxHeight: maxHeight, child: listView);
|
||||
});
|
||||
|
|
|
@ -54,7 +54,7 @@ class DraggableChatWindow extends StatelessWidget {
|
|||
resizeToAvoidBottomInset: false,
|
||||
appBar: CustomAppBar(
|
||||
onPanUpdate: onPanUpdate,
|
||||
appBar: isDesktop
|
||||
appBar: (isDesktop || isWebDesktop)
|
||||
? _buildDesktopAppBar(context)
|
||||
: _buildMobileAppBar(context),
|
||||
),
|
||||
|
|
|
@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/ab_model.dart';
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -52,7 +51,7 @@ class _PeerCardState extends State<_PeerCard>
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
if (isDesktop) {
|
||||
if (isDesktop || isWebDesktop) {
|
||||
return _buildDesktop();
|
||||
} else {
|
||||
return _buildMobile();
|
||||
|
@ -883,8 +882,7 @@ class RecentPeerCard extends BasePeerCard {
|
|||
menuItems.add(_createShortCutAction(peer.id));
|
||||
}
|
||||
menuItems.add(MenuEntryDivider());
|
||||
if (!isWeb) {
|
||||
// TODO: support web version
|
||||
if (isDesktop || isWebDesktop) {
|
||||
menuItems.add(_renameAction(peer.id));
|
||||
}
|
||||
if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
|
@ -940,8 +938,7 @@ class FavoritePeerCard extends BasePeerCard {
|
|||
menuItems.add(_createShortCutAction(peer.id));
|
||||
}
|
||||
menuItems.add(MenuEntryDivider());
|
||||
if (!isWeb) {
|
||||
// TODO: support web version
|
||||
if (isDesktop || isWebDesktop) {
|
||||
menuItems.add(_renameAction(peer.id));
|
||||
}
|
||||
if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
|
@ -1046,8 +1043,7 @@ class AddressBookPeerCard extends BasePeerCard {
|
|||
}
|
||||
if (gFFI.abModel.current.canWrite()) {
|
||||
menuItems.add(MenuEntryDivider());
|
||||
if (!isWeb) {
|
||||
// TODO: support web version
|
||||
if (isDesktop || isWebDesktop) {
|
||||
menuItems.add(_renameAction(peer.id));
|
||||
}
|
||||
if (gFFI.abModel.current.isPersonal() && peer.hash.isNotEmpty) {
|
||||
|
@ -1249,7 +1245,7 @@ void _rdpDialog(String id) async {
|
|||
).marginOnly(bottom: isDesktop ? 8 : 0),
|
||||
Row(
|
||||
children: [
|
||||
isDesktop
|
||||
(isDesktop || isWebDesktop)
|
||||
? ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
child: Text(
|
||||
|
@ -1260,15 +1256,17 @@ void _rdpDialog(String id) async {
|
|||
Expanded(
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: isDesktop ? null : translate('Username')),
|
||||
labelText: (isDesktop || isWebDesktop)
|
||||
? null
|
||||
: translate('Username')),
|
||||
controller: userController,
|
||||
),
|
||||
),
|
||||
],
|
||||
).marginOnly(bottom: isDesktop ? 8 : 0),
|
||||
).marginOnly(bottom: (isDesktop || isWebDesktop) ? 8 : 0),
|
||||
Row(
|
||||
children: [
|
||||
isDesktop
|
||||
(isDesktop || isWebDesktop)
|
||||
? ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
child: Text(
|
||||
|
@ -1280,7 +1278,9 @@ void _rdpDialog(String id) async {
|
|||
child: Obx(() => TextField(
|
||||
obscureText: secure.value,
|
||||
decoration: InputDecoration(
|
||||
labelText: isDesktop ? null : translate('Password'),
|
||||
labelText: (isDesktop || isWebDesktop)
|
||||
? null
|
||||
: translate('Password'),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => secure.value = !secure.value,
|
||||
icon: Icon(secure.value
|
||||
|
|
|
@ -37,7 +37,7 @@ class _TabEntry {
|
|||
}
|
||||
|
||||
EdgeInsets? _menuPadding() {
|
||||
return isDesktop ? kDesktopMenuPadding : null;
|
||||
return (isDesktop || isWebDesktop) ? kDesktopMenuPadding : null;
|
||||
}
|
||||
|
||||
class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
@ -113,7 +113,9 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||
SizedBox(
|
||||
height: 32,
|
||||
child: Container(
|
||||
padding: isDesktop ? null : EdgeInsets.symmetric(horizontal: 2),
|
||||
padding: (isDesktop || isWebDesktop)
|
||||
? null
|
||||
: EdgeInsets.symmetric(horizontal: 2),
|
||||
child: selectionWrap(Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
|
@ -127,7 +129,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||
],
|
||||
)),
|
||||
),
|
||||
).paddingOnly(right: isDesktop ? 12 : 0),
|
||||
).paddingOnly(right: (isDesktop || isWebDesktop) ? 12 : 0),
|
||||
_createPeersView(),
|
||||
],
|
||||
);
|
||||
|
@ -195,7 +197,8 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||
}
|
||||
}
|
||||
return Expanded(
|
||||
child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0));
|
||||
child: child.marginSymmetric(
|
||||
vertical: (isDesktop || isWebDesktop) ? 12.0 : 6.0));
|
||||
}
|
||||
|
||||
Widget _createRefresh(
|
||||
|
|
|
@ -78,7 +78,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
|||
LoadEvent.lan: 'empty_lan_tip',
|
||||
LoadEvent.addressBook: 'empty_address_book_tip',
|
||||
});
|
||||
final space = isDesktop ? 12.0 : 8.0;
|
||||
final space = (isDesktop || isWebDesktop) ? 12.0 : 8.0;
|
||||
final _curPeers = <String>{};
|
||||
var _lastChangeTime = DateTime.now();
|
||||
var _lastQueryPeers = <String>{};
|
||||
|
@ -200,7 +200,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
|||
Provider.of<PeerTabModel>(context, listen: false).currentTab;
|
||||
final hideAbTagsPanel =
|
||||
bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty;
|
||||
return isDesktop
|
||||
return (isDesktop || isWebDesktop)
|
||||
? Obx(
|
||||
() => SizedBox(
|
||||
width: peerCardUiType.value != PeerUiType.list
|
||||
|
|
|
@ -77,7 +77,7 @@ class _RawTouchGestureDetectorRegionState
|
|||
FFI get ffi => widget.ffi;
|
||||
FfiModel get ffiModel => widget.ffiModel;
|
||||
InputModel get inputModel => widget.inputModel;
|
||||
bool get handleTouch => isDesktop || ffiModel.touchMode;
|
||||
bool get handleTouch => (isDesktop || isWebDesktop) || ffiModel.touchMode;
|
||||
SessionID get sessionId => ffi.sessionId;
|
||||
|
||||
@override
|
||||
|
@ -183,7 +183,7 @@ class _RawTouchGestureDetectorRegionState
|
|||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||
return;
|
||||
}
|
||||
if (isDesktop || !ffiModel.touchMode) {
|
||||
if ((isDesktop || isWebDesktop) || !ffiModel.touchMode) {
|
||||
inputModel.tap(MouseButtons.right);
|
||||
}
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ class _RawTouchGestureDetectorRegionState
|
|||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||
return;
|
||||
}
|
||||
if (isDesktop) {
|
||||
if ((isDesktop || isWebDesktop)) {
|
||||
final scale = ((d.scale - _scale) * 1000).toInt();
|
||||
_scale = d.scale;
|
||||
|
||||
|
@ -286,7 +286,7 @@ class _RawTouchGestureDetectorRegionState
|
|||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||
return;
|
||||
}
|
||||
if (isDesktop) {
|
||||
if ((isDesktop || isWebDesktop)) {
|
||||
bind.sessionSendPointer(
|
||||
sessionId: sessionId,
|
||||
msg: json.encode(
|
||||
|
@ -409,7 +409,9 @@ class RawPointerMouseRegion extends StatelessWidget {
|
|||
onPointerPanZoomUpdate: inputModel.onPointerPanZoomUpdate,
|
||||
onPointerPanZoomEnd: inputModel.onPointerPanZoomEnd,
|
||||
child: MouseRegion(
|
||||
cursor: cursor ?? MouseCursor.defer,
|
||||
cursor: inputModel.isViewOnly
|
||||
? MouseCursor.defer
|
||||
: (cursor ?? MouseCursor.defer),
|
||||
onEnter: onEnter,
|
||||
onExit: onExit,
|
||||
child: child,
|
||||
|
|
|
@ -209,10 +209,10 @@ List<Widget> ServerConfigImportExportWidgets(
|
|||
List<(String, String)> otherDefaultSettings() {
|
||||
List<(String, String)> v = [
|
||||
('View Mode', 'view_only'),
|
||||
if (isDesktop) ('show_monitors_tip', kKeyShowMonitorsToolbar),
|
||||
if (isDesktop) ('Collapse toolbar', 'collapse_toolbar'),
|
||||
if ((isDesktop || isWebDesktop)) ('show_monitors_tip', kKeyShowMonitorsToolbar),
|
||||
if ((isDesktop || isWebDesktop)) ('Collapse toolbar', 'collapse_toolbar'),
|
||||
('Show remote cursor', 'show_remote_cursor'),
|
||||
if (isDesktop) ('Zoom cursor', 'zoom-cursor'),
|
||||
if ((isDesktop || isWebDesktop)) ('Zoom cursor', 'zoom-cursor'),
|
||||
('Show quality monitor', 'show_quality_monitor'),
|
||||
('Mute', 'disable_audio'),
|
||||
if (isDesktop) ('Enable file copy and paste', 'enable_file_transfer'),
|
||||
|
|
|
@ -94,7 +94,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
|||
Text(translate(pi.isHeadless ? 'OS Account' : 'OS Password')),
|
||||
]),
|
||||
trailingIcon: Transform.scale(
|
||||
scale: isDesktop ? 0.8 : 1,
|
||||
scale: (isDesktop || isWebDesktop) ? 0.8 : 1,
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
if (isMobile && Navigator.canPop(context)) {
|
||||
|
@ -160,7 +160,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
|||
);
|
||||
}
|
||||
// divider
|
||||
if (isDesktop) {
|
||||
if (isDesktop || isWebDesktop) {
|
||||
v.add(TTextMenu(child: Offstage(), onPressed: () {}, divider: true));
|
||||
}
|
||||
// ctrlAltDel
|
||||
|
@ -229,7 +229,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
|||
));
|
||||
}
|
||||
// record
|
||||
if (!isDesktop &&
|
||||
if (!(isDesktop || isWeb) &&
|
||||
(ffi.recordingModel.start || (perms["recording"] != false))) {
|
||||
v.add(TTextMenu(
|
||||
child: Row(
|
||||
|
@ -250,7 +250,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
|||
onPressed: () => ffi.recordingModel.toggle()));
|
||||
}
|
||||
// fingerprint
|
||||
if (!isDesktop) {
|
||||
if (!(isDesktop || isWebDesktop)) {
|
||||
v.add(TTextMenu(
|
||||
child: Text(translate('Copy Fingerprint')),
|
||||
onPressed: () => onCopyFingerprint(FingerprintState.find(id).value),
|
||||
|
@ -511,8 +511,8 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||
child: Text(translate('Show displays as individual windows'))));
|
||||
}
|
||||
|
||||
final screenList = await getScreenRectList();
|
||||
if (useTextureRender && pi.isSupportMultiDisplay && screenList.length > 1) {
|
||||
final isMultiScreens = !isWeb && (await getScreenRectList()).length > 1;
|
||||
if (useTextureRender && pi.isSupportMultiDisplay && isMultiScreens) {
|
||||
final value = bind.sessionGetUseAllMyDisplaysForTheRemoteSession(
|
||||
sessionId: ffi.sessionId) ==
|
||||
'Y';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
|
|
|
@ -114,7 +114,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
|||
if (!bind.isIncomingOnly())
|
||||
_TabInfo(
|
||||
'Display', Icons.desktop_windows_outlined, Icons.desktop_windows),
|
||||
if (!bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
|
||||
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
|
||||
_TabInfo('Plugin', Icons.extension_outlined, Icons.extension),
|
||||
if (!bind.isDisableAccount())
|
||||
_TabInfo('Account', Icons.person_outline, Icons.person),
|
||||
|
@ -129,7 +129,8 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
|||
if (!bind.isOutgoingOnly() && !bind.isDisableSettings()) _Safety(),
|
||||
if (!bind.isDisableSettings()) _Network(),
|
||||
if (!bind.isIncomingOnly()) _Display(),
|
||||
if (!bind.isIncomingOnly() && bind.pluginFeatureIsEnabled()) _Plugin(),
|
||||
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
|
||||
_Plugin(),
|
||||
if (!bind.isDisableAccount()) _Account(),
|
||||
_About(),
|
||||
];
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
|
|
|
@ -35,13 +35,13 @@ class RemotePage extends StatefulWidget {
|
|||
RemotePage({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.sessionId,
|
||||
required this.tabWindowId,
|
||||
required this.display,
|
||||
required this.displays,
|
||||
required this.password,
|
||||
required this.toolbarState,
|
||||
required this.tabController,
|
||||
this.sessionId,
|
||||
this.tabWindowId,
|
||||
this.password,
|
||||
this.display,
|
||||
this.displays,
|
||||
this.tabController,
|
||||
this.switchUuid,
|
||||
this.forceRelay,
|
||||
this.isSharedPassword,
|
||||
|
@ -58,7 +58,7 @@ class RemotePage extends StatefulWidget {
|
|||
final bool? forceRelay;
|
||||
final bool? isSharedPassword;
|
||||
final SimpleWrapper<State<RemotePage>?> _lastState = SimpleWrapper(null);
|
||||
final DesktopTabController tabController;
|
||||
final DesktopTabController? tabController;
|
||||
|
||||
FFI get ffi => (_lastState.value! as _RemotePageState)._ffi;
|
||||
|
||||
|
@ -129,7 +129,7 @@ class _RemotePageState extends State<RemotePage>
|
|||
}
|
||||
|
||||
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
|
||||
bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
|
||||
if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
|
||||
_ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
||||
// Session option should be set after models.dart/FFI.start
|
||||
_showRemoteCursor.value = bind.sessionGetToggleOptionSync(
|
||||
|
@ -150,7 +150,7 @@ class _RemotePageState extends State<RemotePage>
|
|||
// }
|
||||
|
||||
_blockableOverlayState.applyFfi(_ffi);
|
||||
widget.tabController.onSelected?.call(widget.id);
|
||||
widget.tabController?.onSelected?.call(widget.id);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -431,9 +431,9 @@ class _RemotePageState extends State<RemotePage>
|
|||
Widget getBodyForDesktop(BuildContext context) {
|
||||
var paints = <Widget>[
|
||||
MouseRegion(onEnter: (evt) {
|
||||
bind.hostStopSystemKeyPropagate(stopped: false);
|
||||
if (!isWeb) bind.hostStopSystemKeyPropagate(stopped: false);
|
||||
}, onExit: (evt) {
|
||||
bind.hostStopSystemKeyPropagate(stopped: true);
|
||||
if (!isWeb) bind.hostStopSystemKeyPropagate(stopped: true);
|
||||
}, child: LayoutBuilder(builder: (context, constraints) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
Provider.of<CanvasModel>(context, listen: false).updateViewStyle();
|
||||
|
@ -669,6 +669,11 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||
|
||||
MouseCursor _buildCursorOfCache(
|
||||
CursorModel cursor, double scale, CursorData? cache) {
|
||||
// TODO: web cursor
|
||||
if (isWeb) {
|
||||
return MouseCursor.defer;
|
||||
}
|
||||
|
||||
if (cache == null) {
|
||||
return MouseCursor.defer;
|
||||
} else {
|
||||
|
|
|
@ -491,7 +491,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
|||
toolbarItems.add(_ChatMenu(id: widget.id, ffi: widget.ffi));
|
||||
toolbarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi));
|
||||
}
|
||||
toolbarItems.add(_RecordMenu());
|
||||
if (!isWeb) toolbarItems.add(_RecordMenu());
|
||||
toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi));
|
||||
final toolbarBorderRadius = BorderRadius.all(Radius.circular(4.0));
|
||||
return Column(
|
||||
|
@ -940,13 +940,12 @@ class ScreenAdjustor {
|
|||
}
|
||||
|
||||
updateScreen() async {
|
||||
final v = await rustDeskWinManager.call(
|
||||
WindowType.Main, kWindowGetWindowInfo, '');
|
||||
final String valueStr = v.result;
|
||||
if (valueStr.isEmpty) {
|
||||
final String info =
|
||||
isWeb ? screenInfo : await _getScreenInfoDesktop() ?? '';
|
||||
if (info.isEmpty) {
|
||||
_screen = null;
|
||||
} else {
|
||||
final screenMap = jsonDecode(valueStr);
|
||||
final screenMap = jsonDecode(info);
|
||||
_screen = window_size.Screen(
|
||||
Rect.fromLTRB(screenMap['frame']['l'], screenMap['frame']['t'],
|
||||
screenMap['frame']['r'], screenMap['frame']['b']),
|
||||
|
@ -959,15 +958,23 @@ class ScreenAdjustor {
|
|||
}
|
||||
}
|
||||
|
||||
_getScreenInfoDesktop() async {
|
||||
final v = await rustDeskWinManager.call(
|
||||
WindowType.Main, kWindowGetWindowInfo, '');
|
||||
return v.result;
|
||||
}
|
||||
|
||||
Future<bool> isWindowCanBeAdjusted() async {
|
||||
final viewStyle =
|
||||
await bind.sessionGetViewStyle(sessionId: ffi.sessionId) ?? '';
|
||||
if (viewStyle != kRemoteViewStyleOriginal) {
|
||||
return false;
|
||||
}
|
||||
final remoteCount = RemoteCountState.find().value;
|
||||
if (remoteCount != 1) {
|
||||
return false;
|
||||
if (!isWeb) {
|
||||
final remoteCount = RemoteCountState.find().value;
|
||||
if (remoteCount != 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (_screen == null) {
|
||||
return false;
|
||||
|
@ -1325,6 +1332,14 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||
final display = json.decode(mainDisplay);
|
||||
if (display['w'] != null && display['h'] != null) {
|
||||
_localResolution = Resolution(display['w'], display['h']);
|
||||
if (isWeb) {
|
||||
if (display['scaleFactor'] != null) {
|
||||
_localResolution = Resolution(
|
||||
(display['w'] / display['scaleFactor']).toInt(),
|
||||
(display['h'] / display['scaleFactor']).toInt(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Failed to decode $mainDisplay, $e');
|
||||
|
|
|
@ -125,10 +125,7 @@ void runMainApp(bool startService) async {
|
|||
await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]);
|
||||
gFFI.userModel.refreshCurrentUser();
|
||||
runApp(App());
|
||||
if (isWeb) {
|
||||
// Web does not support window manager.
|
||||
return;
|
||||
}
|
||||
|
||||
// Set window option.
|
||||
WindowOptions windowOptions = getHiddenTitleBarWindowOptions();
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
|
|
|
@ -3,7 +3,6 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
||||
import 'package:flutter_hbb/models/file_model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:toggle_switch/toggle_switch.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
|
|
@ -193,6 +193,7 @@ class InputModel {
|
|||
bool get keyboardPerm => parent.target!.ffiModel.keyboard;
|
||||
String get id => parent.target?.id ?? '';
|
||||
String? get peerPlatform => parent.target?.ffiModel.pi.platform;
|
||||
bool get isViewOnly => parent.target!.ffiModel.viewOnly;
|
||||
|
||||
InputModel(this.parent) {
|
||||
sessionId = parent.target!.sessionId;
|
||||
|
@ -207,7 +208,7 @@ class InputModel {
|
|||
|
||||
updateKeyboardMode() async {
|
||||
// * Currently mobile does not enable map mode
|
||||
if (isDesktop) {
|
||||
if (isDesktop || isWebDesktop) {
|
||||
if (keyboardMode.isEmpty) {
|
||||
keyboardMode =
|
||||
await bind.sessionGetKeyboardMode(sessionId: sessionId) ??
|
||||
|
@ -217,7 +218,8 @@ class InputModel {
|
|||
}
|
||||
|
||||
KeyEventResult handleRawKeyEvent(RawKeyEvent e) {
|
||||
if (isDesktop && !isInputSourceFlutter) {
|
||||
if (isViewOnly) return KeyEventResult.handled;
|
||||
if ((isDesktop || isWebDesktop) && !isInputSourceFlutter) {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
|
@ -256,7 +258,7 @@ class InputModel {
|
|||
}
|
||||
|
||||
// * Currently mobile does not enable map mode
|
||||
if (isDesktop && keyboardMode == 'map') {
|
||||
if ((isDesktop || isWebDesktop) && keyboardMode == 'map') {
|
||||
mapKeyboardMode(e);
|
||||
} else {
|
||||
legacyKeyboardMode(e);
|
||||
|
@ -467,6 +469,7 @@ class InputModel {
|
|||
|
||||
void onPointHoverImage(PointerHoverEvent e) {
|
||||
_stopFling = true;
|
||||
if (isViewOnly) return;
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (!isPhysicalMouse.value) {
|
||||
isPhysicalMouse.value = true;
|
||||
|
@ -479,7 +482,7 @@ class InputModel {
|
|||
void onPointerPanZoomStart(PointerPanZoomStartEvent e) {
|
||||
_lastScale = 1.0;
|
||||
_stopFling = true;
|
||||
|
||||
if (isViewOnly) return;
|
||||
if (peerPlatform == kPeerPlatformAndroid) {
|
||||
handlePointerEvent('touch', 'pan_start', e.position);
|
||||
}
|
||||
|
@ -487,6 +490,7 @@ class InputModel {
|
|||
|
||||
// https://docs.flutter.dev/release/breaking-changes/trackpad-gestures
|
||||
void onPointerPanZoomUpdate(PointerPanZoomUpdateEvent e) {
|
||||
if (isViewOnly) return;
|
||||
if (peerPlatform != kPeerPlatformAndroid) {
|
||||
final scale = ((e.scale - _lastScale) * 1000).toInt();
|
||||
_lastScale = e.scale;
|
||||
|
@ -612,6 +616,7 @@ class InputModel {
|
|||
void onPointDownImage(PointerDownEvent e) {
|
||||
debugPrint("onPointDownImage ${e.kind}");
|
||||
_stopFling = true;
|
||||
if (isViewOnly) return;
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) {
|
||||
if (isPhysicalMouse.value) {
|
||||
isPhysicalMouse.value = false;
|
||||
|
@ -623,6 +628,7 @@ class InputModel {
|
|||
}
|
||||
|
||||
void onPointUpImage(PointerUpEvent e) {
|
||||
if (isViewOnly) return;
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (isPhysicalMouse.value) {
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventUp), e.position);
|
||||
|
@ -630,6 +636,7 @@ class InputModel {
|
|||
}
|
||||
|
||||
void onPointMoveImage(PointerMoveEvent e) {
|
||||
if (isViewOnly) return;
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (isPhysicalMouse.value) {
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position);
|
||||
|
@ -637,6 +644,7 @@ class InputModel {
|
|||
}
|
||||
|
||||
void onPointerSignalImage(PointerSignalEvent e) {
|
||||
if (isViewOnly) return;
|
||||
if (e is PointerScrollEvent) {
|
||||
var dx = e.scrollDelta.dx.toInt();
|
||||
var dy = e.scrollDelta.dy.toInt();
|
||||
|
@ -902,9 +910,11 @@ class InputModel {
|
|||
int minX = rect.left.toInt();
|
||||
// https://github.com/rustdesk/rustdesk/issues/6678
|
||||
// For Windows, [0,maxX], [0,maxY] should be set to enable window snapping.
|
||||
int maxX = (rect.left + rect.width).toInt() - (peerPlatform == kPeerPlatformWindows ? 0 : 1);
|
||||
int maxX = (rect.left + rect.width).toInt() -
|
||||
(peerPlatform == kPeerPlatformWindows ? 0 : 1);
|
||||
int minY = rect.top.toInt();
|
||||
int maxY = (rect.top + rect.height).toInt() - (peerPlatform == kPeerPlatformWindows ? 0 : 1);
|
||||
int maxY = (rect.top + rect.height).toInt() -
|
||||
(peerPlatform == kPeerPlatformWindows ? 0 : 1);
|
||||
evtX = trySetNearestRange(evtX, minX, maxX, 5);
|
||||
evtY = trySetNearestRange(evtY, minY, maxY, 5);
|
||||
if (kind == kPointerEventKindMouse) {
|
||||
|
|
|
@ -429,7 +429,7 @@ class FfiModel with ChangeNotifier {
|
|||
}
|
||||
|
||||
handleAliasChanged(Map<String, dynamic> evt) {
|
||||
if (!isDesktop) return;
|
||||
if (!(isDesktop || isWebDesktop)) return;
|
||||
final String peerId = evt['id'];
|
||||
final String alias = evt['alias'];
|
||||
String label = getDesktopTabLabel(peerId, alias);
|
||||
|
@ -767,7 +767,7 @@ class FfiModel with ChangeNotifier {
|
|||
_pi.isSet.value = true;
|
||||
stateGlobal.resetLastResolutionGroupValues(peerId);
|
||||
|
||||
if (isDesktop) {
|
||||
if (isDesktop || isWebDesktop) {
|
||||
checkDesktopKeyboardMode();
|
||||
}
|
||||
|
||||
|
@ -1114,7 +1114,7 @@ class ImageModel with ChangeNotifier {
|
|||
|
||||
update(ui.Image? image) async {
|
||||
if (_image == null && image != null) {
|
||||
if (isWebDesktop || isDesktop) {
|
||||
if (isDesktop || isWebDesktop) {
|
||||
await parent.target?.canvasModel.updateViewStyle();
|
||||
await parent.target?.canvasModel.updateScrollStyle();
|
||||
} else {
|
||||
|
@ -1288,18 +1288,15 @@ class CanvasModel with ChangeNotifier {
|
|||
double get scrollX => _scrollX;
|
||||
double get scrollY => _scrollY;
|
||||
|
||||
static double get leftToEdge => (isDesktop || isWebDesktop)
|
||||
? windowBorderWidth + kDragToResizeAreaPadding.left
|
||||
: 0;
|
||||
static double get rightToEdge => (isDesktop || isWebDesktop)
|
||||
? windowBorderWidth + kDragToResizeAreaPadding.right
|
||||
: 0;
|
||||
static double get topToEdge => (isDesktop || isWebDesktop)
|
||||
static double get leftToEdge =>
|
||||
isDesktop ? windowBorderWidth + kDragToResizeAreaPadding.left : 0;
|
||||
static double get rightToEdge =>
|
||||
isDesktop ? windowBorderWidth + kDragToResizeAreaPadding.right : 0;
|
||||
static double get topToEdge => isDesktop
|
||||
? tabBarHeight + windowBorderWidth + kDragToResizeAreaPadding.top
|
||||
: 0;
|
||||
static double get bottomToEdge => (isDesktop || isWebDesktop)
|
||||
? windowBorderWidth + kDragToResizeAreaPadding.bottom
|
||||
: 0;
|
||||
static double get bottomToEdge =>
|
||||
isDesktop ? windowBorderWidth + kDragToResizeAreaPadding.bottom : 0;
|
||||
|
||||
updateViewStyle({refreshMousePos = true}) async {
|
||||
Size getSize() {
|
||||
|
@ -1422,7 +1419,7 @@ class CanvasModel with ChangeNotifier {
|
|||
// If keyboard is not permitted, do not move cursor when mouse is moving.
|
||||
if (parent.target != null && parent.target!.ffiModel.keyboard) {
|
||||
// Draw cursor if is not desktop.
|
||||
if (!isDesktop) {
|
||||
if (!(isDesktop || isWebDesktop)) {
|
||||
parent.target!.cursorModel.moveLocal(x, y);
|
||||
} else {
|
||||
try {
|
||||
|
@ -2495,7 +2492,8 @@ class PeerInfo with ChangeNotifier {
|
|||
List<int> get virtualDisplays => List<int>.from(
|
||||
platformAdditions[kPlatformAdditionsVirtualDisplays] ?? []);
|
||||
|
||||
bool get isSupportMultiDisplay => isDesktop && isSupportMultiUiSession;
|
||||
bool get isSupportMultiDisplay =>
|
||||
(isDesktop || isWebDesktop) && isSupportMultiUiSession;
|
||||
|
||||
bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false;
|
||||
|
||||
|
|
|
@ -98,9 +98,11 @@ class PlatformFFI {
|
|||
sessionId: sessionId, display: display, ptr: ptr);
|
||||
|
||||
Future<void> init(String appType) async {
|
||||
isWebDesktop = !context.callMethod('isMobile');
|
||||
context.callMethod('init');
|
||||
version = getByName('version');
|
||||
window.onContextMenu.listen((event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
context['onRegisteredEvent'] = (String message) {
|
||||
try {
|
||||
|
|
|
@ -6,5 +6,8 @@ final isWindows_ = Platform.isWindows;
|
|||
final isMacOS_ = Platform.isMacOS;
|
||||
final isLinux_ = Platform.isLinux;
|
||||
final isWeb_ = false;
|
||||
final isWebDesktop_ = false;
|
||||
|
||||
final isDesktop_ = Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
||||
|
||||
String get screenInfo_ => '';
|
||||
|
|
|
@ -3,6 +3,8 @@ import 'dart:ui' as ui;
|
|||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
|
||||
Future<ui.Image> decodeImageFromPixels(
|
||||
Uint8List pixels,
|
||||
int width,
|
||||
|
@ -79,6 +81,11 @@ class ImagePainter extends CustomPainter {
|
|||
paint.filterQuality = FilterQuality.high;
|
||||
}
|
||||
}
|
||||
// It's strange that if (scale < 0.5 && paint.filterQuality == FilterQuality.medium)
|
||||
// The canvas.drawImage will not work on web
|
||||
if (isWeb) {
|
||||
paint.filterQuality = FilterQuality.high;
|
||||
}
|
||||
canvas.drawImage(
|
||||
image!, Offset(x.toInt().toDouble(), y.toInt().toDouble()), paint);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import 'dart:typed_data';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
|
@ -237,7 +239,6 @@ class RustdeskImpl {
|
|||
|
||||
Future<String?> sessionGetViewStyle(
|
||||
{required UuidValue sessionId, dynamic hint}) {
|
||||
// TODO: default values
|
||||
return Future(() =>
|
||||
js.context.callMethod('getByName', ['option:session', 'view_style']));
|
||||
}
|
||||
|
@ -252,7 +253,6 @@ class RustdeskImpl {
|
|||
|
||||
Future<String?> sessionGetScrollStyle(
|
||||
{required UuidValue sessionId, dynamic hint}) {
|
||||
// TODO: default values
|
||||
return Future(() =>
|
||||
js.context.callMethod('getByName', ['option:session', 'scroll_style']));
|
||||
}
|
||||
|
@ -266,9 +266,7 @@ class RustdeskImpl {
|
|||
}
|
||||
|
||||
Future<String?> sessionGetImageQuality(
|
||||
// TODO: default values
|
||||
{required UuidValue sessionId,
|
||||
dynamic hint}) {
|
||||
{required UuidValue sessionId, dynamic hint}) {
|
||||
return Future(() => js.context
|
||||
.callMethod('getByName', ['option:session', 'image_quality']));
|
||||
}
|
||||
|
@ -283,9 +281,9 @@ class RustdeskImpl {
|
|||
|
||||
Future<String?> sessionGetKeyboardMode(
|
||||
{required UuidValue sessionId, dynamic hint}) {
|
||||
// TODO: default values
|
||||
return Future(() => js.context
|
||||
.callMethod('getByName', ['option:session', 'keyboard_mode']));
|
||||
final mode =
|
||||
js.context.callMethod('getByName', ['option:session', 'keyboard_mode']);
|
||||
return Future(() => mode == '' ? null : mode);
|
||||
}
|
||||
|
||||
Future<void> sessionSetKeyboardMode(
|
||||
|
@ -345,7 +343,7 @@ class RustdeskImpl {
|
|||
|
||||
bool sessionIsKeyboardModeSupported(
|
||||
{required UuidValue sessionId, required String mode, dynamic hint}) {
|
||||
throw UnimplementedError();
|
||||
return mode == kKeyLegacyMode;
|
||||
}
|
||||
|
||||
Future<void> sessionSetCustomImageQuality(
|
||||
|
@ -748,8 +746,7 @@ class RustdeskImpl {
|
|||
}
|
||||
|
||||
Future<void> mainCheckConnectStatus({dynamic hint}) {
|
||||
return Future(
|
||||
() => js.context.callMethod('setByName', ["check_conn_status"]));
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Future<bool> mainIsUsingPublicServer({dynamic hint}) {
|
||||
|
@ -929,12 +926,14 @@ class RustdeskImpl {
|
|||
|
||||
Future<void> mainSetUserDefaultOption(
|
||||
{required String key, required String value, dynamic hint}) {
|
||||
// TODO: do we need the default option?
|
||||
throw UnimplementedError();
|
||||
return js.context.callMethod('getByName', [
|
||||
'option:user:default',
|
||||
jsonEncode({'name': key, 'value': value})
|
||||
]);
|
||||
}
|
||||
|
||||
String mainGetUserDefaultOption({required String key, dynamic hint}) {
|
||||
throw UnimplementedError();
|
||||
return js.context.callMethod('getByName', ['option:user:default', key]);
|
||||
}
|
||||
|
||||
Future<String> mainHandleRelayId({required String id, dynamic hint}) {
|
||||
|
@ -946,7 +945,7 @@ class RustdeskImpl {
|
|||
}
|
||||
|
||||
String mainGetMainDisplay({dynamic hint}) {
|
||||
throw UnimplementedError();
|
||||
return js.context.callMethod('getByName', ['main_display']);
|
||||
}
|
||||
|
||||
String mainGetDisplays({dynamic hint}) {
|
||||
|
@ -1399,7 +1398,7 @@ class RustdeskImpl {
|
|||
}
|
||||
|
||||
bool mainHasPixelbufferTextureRender({dynamic hint}) {
|
||||
throw UnimplementedError();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mainHasFileClipboard({dynamic hint}) {
|
||||
|
@ -1553,7 +1552,9 @@ class RustdeskImpl {
|
|||
}
|
||||
|
||||
String mainSupportedInputSource({dynamic hint}) {
|
||||
return jsonEncode(['Input source 2', 'input_source_2_tip']);
|
||||
return jsonEncode([
|
||||
['Input source 2', 'input_source_2_tip']
|
||||
]);
|
||||
}
|
||||
|
||||
Future<String> mainGenerate2Fa({dynamic hint}) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:js' as js;
|
||||
|
||||
final isAndroid_ = false;
|
||||
final isIOS_ = false;
|
||||
|
@ -5,5 +6,8 @@ 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']);
|
||||
|
|
|
@ -652,7 +652,7 @@ export default class Connection {
|
|||
}
|
||||
|
||||
getOption(name: string): any {
|
||||
return this._options[name];
|
||||
return this._options[name] ?? globals.getUserDefaultOption(name);
|
||||
}
|
||||
|
||||
getToggleOption(name: string): Boolean {
|
||||
|
@ -839,6 +839,52 @@ export default class Connection {
|
|||
}
|
||||
|
||||
toggleOption(name: string) {
|
||||
|
||||
// } else if name == "block-input" {
|
||||
// option.block_input = BoolOption::Yes.into();
|
||||
// } else if name == "unblock-input" {
|
||||
// option.block_input = BoolOption::No.into();
|
||||
// } else if name == "show-quality-monitor" {
|
||||
// config.show_quality_monitor.v = !config.show_quality_monitor.v;
|
||||
// } else if name == "allow_swap_key" {
|
||||
// config.allow_swap_key.v = !config.allow_swap_key.v;
|
||||
// } else if name == "view-only" {
|
||||
// config.view_only.v = !config.view_only.v;
|
||||
// let f = |b: bool| {
|
||||
// if b {
|
||||
// BoolOption::Yes.into()
|
||||
// } else {
|
||||
// BoolOption::No.into()
|
||||
// }
|
||||
// };
|
||||
// if config.view_only.v {
|
||||
// option.disable_keyboard = f(true);
|
||||
// option.disable_clipboard = f(true);
|
||||
// option.show_remote_cursor = f(true);
|
||||
// option.enable_file_transfer = f(false);
|
||||
// option.lock_after_session_end = f(false);
|
||||
// } else {
|
||||
// option.disable_keyboard = f(false);
|
||||
// option.disable_clipboard = f(self.get_toggle_option("disable-clipboard"));
|
||||
// option.show_remote_cursor = f(self.get_toggle_option("show-remote-cursor"));
|
||||
// option.enable_file_transfer = f(self.config.enable_file_transfer.v);
|
||||
// option.lock_after_session_end = f(self.config.lock_after_session_end.v);
|
||||
// }
|
||||
// } else {
|
||||
// let is_set = self
|
||||
// .options
|
||||
// .get(&name)
|
||||
// .map(|o| !o.is_empty())
|
||||
// .unwrap_or(false);
|
||||
// if is_set {
|
||||
// self.config.options.remove(&name);
|
||||
// } else {
|
||||
// self.config.options.insert(name, "Y".to_owned());
|
||||
// }
|
||||
// self.config.store(&self.id);
|
||||
// return None;
|
||||
// }
|
||||
|
||||
const v = !this._options[name];
|
||||
const option = message.OptionMessage.fromPartial({});
|
||||
const v2 = v
|
||||
|
@ -860,13 +906,43 @@ export default class Connection {
|
|||
case "privacy-mode":
|
||||
option.privacy_mode = v2;
|
||||
break;
|
||||
case "enable-file-transfer":
|
||||
option.enable_file_transfer = v2;
|
||||
break;
|
||||
case "block-input":
|
||||
option.block_input = message.OptionMessage_BoolOption.Yes;
|
||||
break;
|
||||
case "unblock-input":
|
||||
option.block_input = message.OptionMessage_BoolOption.No;
|
||||
break;
|
||||
case "show-quality-monitor":
|
||||
case "allow-swap-key":
|
||||
break;
|
||||
case "view-only":
|
||||
if (v) {
|
||||
option.disable_keyboard = message.OptionMessage_BoolOption.Yes;
|
||||
option.disable_clipboard = message.OptionMessage_BoolOption.Yes;
|
||||
option.show_remote_cursor = message.OptionMessage_BoolOption.Yes;
|
||||
option.enable_file_transfer = message.OptionMessage_BoolOption.No;
|
||||
option.lock_after_session_end = message.OptionMessage_BoolOption.No;
|
||||
} else {
|
||||
option.disable_keyboard = message.OptionMessage_BoolOption.No;
|
||||
option.disable_clipboard = this.getToggleOption("disable-clipboard")
|
||||
? message.OptionMessage_BoolOption.Yes
|
||||
: message.OptionMessage_BoolOption.No;
|
||||
option.show_remote_cursor = this.getToggleOption("show-remote-cursor")
|
||||
? message.OptionMessage_BoolOption.Yes
|
||||
: message.OptionMessage_BoolOption.No;
|
||||
option.enable_file_transfer = this.getToggleOption("enable-file-transfer")
|
||||
? message.OptionMessage_BoolOption.Yes
|
||||
: message.OptionMessage_BoolOption.No;
|
||||
option.lock_after_session_end = this.getToggleOption("lock-after-session-end")
|
||||
? message.OptionMessage_BoolOption.Yes
|
||||
: message.OptionMessage_BoolOption.No;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.setOption(name, this._options[name] ? undefined : "Y");
|
||||
return;
|
||||
}
|
||||
if (name.indexOf("block-input") < 0) this.setOption(name, v);
|
||||
|
|
|
@ -211,7 +211,7 @@ window.setByName = (name, value) => {
|
|||
curConn.refresh();
|
||||
break;
|
||||
case 'reconnect':
|
||||
curConn.reconnect();
|
||||
curConn?.reconnect();
|
||||
break;
|
||||
case 'toggle_option':
|
||||
curConn.toggleOption(value);
|
||||
|
@ -244,6 +244,7 @@ window.setByName = (name, value) => {
|
|||
curConn.inputString(value);
|
||||
break;
|
||||
case 'send_mouse':
|
||||
if (!curConn) return;
|
||||
let mask = 0;
|
||||
value = JSON.parse(value);
|
||||
switch (value.type) {
|
||||
|
@ -288,6 +289,9 @@ window.setByName = (name, value) => {
|
|||
value = JSON.parse(value);
|
||||
localStorage.setItem(name + ':' + value.name, value.value);
|
||||
break;
|
||||
case 'option:user:default':
|
||||
setUserDefaultOption(value);
|
||||
break;
|
||||
case 'option:session':
|
||||
value = JSON.parse(value);
|
||||
curConn.setOption(value.name, value.value);
|
||||
|
@ -295,12 +299,11 @@ window.setByName = (name, value) => {
|
|||
case 'option:peer':
|
||||
setPeerOption(value);
|
||||
break;
|
||||
case 'option:toggle':
|
||||
return curConn.toggleOption(value);
|
||||
case 'input_os_password':
|
||||
curConn.inputOsPassword(value);
|
||||
break;
|
||||
case 'check_conn_status':
|
||||
curConn.checkConnStatus();
|
||||
break;
|
||||
case 'session_add_sync':
|
||||
return sessionAdd(value);
|
||||
case 'session_start':
|
||||
|
@ -374,8 +377,14 @@ function _getByName(name, arg) {
|
|||
case 'translate':
|
||||
arg = JSON.parse(arg);
|
||||
return translate(arg.locale, arg.text);
|
||||
case 'option:user:default':
|
||||
return getUserDefaultOption(arg);
|
||||
case 'option:session':
|
||||
return curConn.getOption(arg);
|
||||
if (curConn) {
|
||||
return curConn.getOption(arg);
|
||||
} else {
|
||||
return getUserDefaultOption(arg);
|
||||
}
|
||||
case 'option:peer':
|
||||
return getPeerOption(arg);
|
||||
case 'option:toggle':
|
||||
|
@ -412,6 +421,28 @@ function _getByName(name, arg) {
|
|||
return getAuditServer(arg);
|
||||
case 'alternative_codecs':
|
||||
return getAlternativeCodecs();
|
||||
case 'screen_info':
|
||||
return JSON.stringify({
|
||||
frame: {
|
||||
l: window.screenX,
|
||||
t: window.screenY,
|
||||
r: window.screenX + window.innerWidth,
|
||||
b: window.screenY + window.innerHeight,
|
||||
},
|
||||
visibleFrame: {
|
||||
l: window.screen.availLeft,
|
||||
t: window.screen.availTop,
|
||||
r: window.screen.availLeft + window.screen.availWidth,
|
||||
b: window.screen.availTop + window.screen.availHeight,
|
||||
},
|
||||
scaleFactor: window.devicePixelRatio,
|
||||
});
|
||||
case 'main_display':
|
||||
return JSON.stringify({
|
||||
w: window.screen.availWidth,
|
||||
h: window.screen.availHeight,
|
||||
scaleFactor: window.devicePixelRatio,
|
||||
});
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
@ -521,20 +552,52 @@ export function getVersionNumber(v) {
|
|||
}
|
||||
}
|
||||
|
||||
// ========================== options begin ==========================
|
||||
function setUserDefaultOption(value) {
|
||||
try {
|
||||
const ojb = JSON.parse(value);
|
||||
const userDefaultOptions = JSON.parse(localStorage.getItem('user-default-options')) || {};
|
||||
userDefaultOptions[ojb.name] = ojb.value;
|
||||
localStorage.setItem('user-default-options', JSON.stringify(userDefaultOptions));
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to set user default options: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function getUserDefaultOption(value) {
|
||||
const defaultOptions = {
|
||||
'view_style': 'original',
|
||||
'scroll_style': 'scrollauto',
|
||||
'image_quality': 'balanced',
|
||||
'codec-preference': 'auto',
|
||||
'custom_image_quality': '50',
|
||||
'custom-fps': '30',
|
||||
};
|
||||
try {
|
||||
const userDefaultOptions = JSON.parse(localStorage.getItem('user-default-options')) || {};
|
||||
return userDefaultOptions[value] || defaultOptions[value] || '';
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to get user default options: ' + e.message);
|
||||
return defaultOptions[value] || '';
|
||||
}
|
||||
}
|
||||
|
||||
function getPeerOption(value) {
|
||||
try {
|
||||
const obj = JSON.parse(value);
|
||||
const options = getPeers()[obj.id] || {};
|
||||
return options[obj.name] || '';
|
||||
return options[obj.name] ?? getUserDefaultOption(obj.name);
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to get peer option: "' + value + '", ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function setPeerOption(value) {
|
||||
function setPeerOption(param) {
|
||||
try {
|
||||
const obj = JSON.parse(value);
|
||||
const obj = JSON.parse(param);
|
||||
const id = obj.id;
|
||||
const name = obj.name;
|
||||
const value = obj.value;
|
||||
|
@ -554,6 +617,7 @@ function setPeerOption(value) {
|
|||
console.error('Failed to set peer option: "' + value + '", ' + e.message);
|
||||
}
|
||||
}
|
||||
// ========================= options end ===========================
|
||||
|
||||
// ========================== peers begin ==========================
|
||||
function getRecentPeers() {
|
||||
|
@ -668,10 +732,10 @@ function increasePort(host, offset) {
|
|||
|
||||
function getAlternativeCodecs() {
|
||||
return JSON.stringify({
|
||||
vp8: 1,
|
||||
av1: 0,
|
||||
h264: 1,
|
||||
h265: 1,
|
||||
vp8: true,
|
||||
av1: false,
|
||||
h264: false,
|
||||
h265: false,
|
||||
});
|
||||
}
|
||||
// ========================== settings end ===========================
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue