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:
fufesou 2024-03-28 11:38:11 +08:00 committed by GitHub
parent 810b980e6b
commit 6e44a91d0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 362 additions and 153 deletions

View file

@ -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(

View file

@ -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);
});

View file

@ -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),
],
),

View file

@ -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);
});

View file

@ -54,7 +54,7 @@ class DraggableChatWindow extends StatelessWidget {
resizeToAvoidBottomInset: false,
appBar: CustomAppBar(
onPanUpdate: onPanUpdate,
appBar: isDesktop
appBar: (isDesktop || isWebDesktop)
? _buildDesktopAppBar(context)
: _buildMobileAppBar(context),
),

View file

@ -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

View file

@ -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(

View file

@ -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

View file

@ -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,

View file

@ -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'),

View file

@ -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';

View file

@ -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';

View file

@ -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(),
];

View file

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';

View file

@ -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 {

View file

@ -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');

View file

@ -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 {

View file

@ -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';

View file

@ -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) {

View file

@ -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;

View file

@ -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 {

View file

@ -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_ => '';

View file

@ -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);
}

View file

@ -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}) {

View file

@ -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']);

View file

@ -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);

View file

@ -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 ===========================