mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge remote-tracking branch 'github/master' into sigma
This commit is contained in:
@@ -189,7 +189,9 @@ class MyTheme {
|
||||
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
|
||||
)
|
||||
: null,
|
||||
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue).copyWith(
|
||||
colorScheme: ColorScheme.fromSwatch(
|
||||
primarySwatch: Colors.blue,
|
||||
).copyWith(
|
||||
brightness: Brightness.light,
|
||||
background: Color(0xFFEEEEEE),
|
||||
),
|
||||
@@ -229,9 +231,11 @@ class MyTheme {
|
||||
checkboxTheme:
|
||||
const CheckboxThemeData(checkColor: MaterialStatePropertyAll(dark)),
|
||||
colorScheme: ColorScheme.fromSwatch(
|
||||
brightness: Brightness.dark,
|
||||
primarySwatch: Colors.blue,
|
||||
).copyWith(background: Color(0xFF24252B)),
|
||||
).copyWith(
|
||||
brightness: Brightness.dark,
|
||||
background: Color(0xFF24252B),
|
||||
),
|
||||
).copyWith(
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
ColorThemeExtension.dark,
|
||||
@@ -1453,10 +1457,12 @@ connectMainDesktop(String id,
|
||||
connect(BuildContext context, String id,
|
||||
{bool isFileTransfer = false,
|
||||
bool isTcpTunneling = false,
|
||||
bool isRDP = false,
|
||||
bool forceRelay = false}) async {
|
||||
bool isRDP = false}) async {
|
||||
if (id == '') return;
|
||||
id = id.replaceAll(' ', '');
|
||||
final oldId = id;
|
||||
id = await bind.mainHandleRelayId(id: id);
|
||||
final forceRelay = id != oldId;
|
||||
assert(!(isFileTransfer && isTcpTunneling && isRDP),
|
||||
"more than one connect type");
|
||||
|
||||
|
||||
@@ -534,7 +534,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
proc: () {
|
||||
() async {
|
||||
if (isLan) {
|
||||
// TODO
|
||||
bind.mainRemoveDiscovered(id: id);
|
||||
} else {
|
||||
final favs = (await bind.mainGetFav()).toList();
|
||||
if (favs.remove(id)) {
|
||||
@@ -745,12 +745,9 @@ class RecentPeerCard extends BasePeerCard {
|
||||
}
|
||||
|
||||
if (gFFI.userModel.userName.isNotEmpty) {
|
||||
// if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
// menuItems.add(_addToAb(peer));
|
||||
// } else {
|
||||
// menuItems.add(_removeFromAb(peer));
|
||||
// }
|
||||
menuItems.add(_addToAb(peer));
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
@@ -797,12 +794,9 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
}));
|
||||
|
||||
if (gFFI.userModel.userName.isNotEmpty) {
|
||||
// if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
// menuItems.add(_addToAb(peer));
|
||||
// } else {
|
||||
// menuItems.add(_removeFromAb(peer));
|
||||
// }
|
||||
menuItems.add(_addToAb(peer));
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
@@ -843,23 +837,27 @@ class DiscoveredPeerCard extends BasePeerCard {
|
||||
menuItems.add(_createShortCutAction(peer.id));
|
||||
}
|
||||
|
||||
if (!favs.contains(peer.id)) {
|
||||
menuItems.add(_addFavAction(peer.id));
|
||||
} else {
|
||||
menuItems.add(_rmFavAction(peer.id, () async {}));
|
||||
final inRecent = await bind.mainIsInRecentPeers(id: peer.id);
|
||||
if (inRecent) {
|
||||
if (!favs.contains(peer.id)) {
|
||||
menuItems.add(_addFavAction(peer.id));
|
||||
} else {
|
||||
menuItems.add(_rmFavAction(peer.id, () async {}));
|
||||
}
|
||||
}
|
||||
|
||||
if (gFFI.userModel.userName.isNotEmpty) {
|
||||
// if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
// menuItems.add(_addToAb(peer));
|
||||
// } else {
|
||||
// menuItems.add(_removeFromAb(peer));
|
||||
// }
|
||||
menuItems.add(_addToAb(peer));
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_removeAction(peer.id, () async {}));
|
||||
menuItems.add(
|
||||
_removeAction(peer.id, () async {
|
||||
await bind.mainLoadLanPeers();
|
||||
}, isLan: true),
|
||||
);
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
|
||||
const double kDesktopRemoteTabBarHeight = 28.0;
|
||||
const int kMainWindowId = 0;
|
||||
@@ -58,6 +59,11 @@ const double kDesktopFileTransferMaximumWidth = 300;
|
||||
const double kDesktopFileTransferRowHeight = 30.0;
|
||||
const double kDesktopFileTransferHeaderHeight = 25.0;
|
||||
|
||||
EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && Platform.isLinux
|
||||
? stateGlobal.fullscreen || stateGlobal.maximize
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.all(5.0)
|
||||
: EdgeInsets.zero;
|
||||
// https://en.wikipedia.org/wiki/Non-breaking_space
|
||||
const int $nbsp = 0x00A0;
|
||||
|
||||
@@ -79,6 +85,7 @@ const kDefaultScrollAmountMultiplier = 5.0;
|
||||
const kDefaultScrollDuration = Duration(milliseconds: 50);
|
||||
const kDefaultMouseWheelThrottleDuration = Duration(milliseconds: 50);
|
||||
const kFullScreenEdgeSize = 0.0;
|
||||
const kMaximizeEdgeSize = 0.0;
|
||||
var kWindowEdgeSize = Platform.isWindows ? 1.0 : 5.0;
|
||||
const kWindowBorderWidth = 1.0;
|
||||
const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0);
|
||||
|
||||
@@ -151,10 +151,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
/// Connects to the selected peer.
|
||||
void onConnect({bool isFileTransfer = false}) {
|
||||
var id = _idController.id;
|
||||
var forceRelay = id.endsWith(r'/r');
|
||||
if (forceRelay) id = id.substring(0, id.length - 2);
|
||||
connect(context, id,
|
||||
isFileTransfer: isFileTransfer, forceRelay: forceRelay);
|
||||
connect(context, id, isFileTransfer: isFileTransfer);
|
||||
}
|
||||
|
||||
/// UI for the remote ID TextField.
|
||||
|
||||
@@ -75,7 +75,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
isClose: false,
|
||||
),
|
||||
)));
|
||||
return Platform.isMacOS
|
||||
return Platform.isMacOS || kUseCompatibleUiMode
|
||||
? tabWidget
|
||||
: Obx(
|
||||
() => DragToResizeArea(
|
||||
|
||||
@@ -567,161 +567,187 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
return false;
|
||||
}
|
||||
|
||||
Widget generateCard(Widget child) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(15.0),
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// transfer status list
|
||||
/// watch transfer status
|
||||
Widget statusList() {
|
||||
return PreferredSize(
|
||||
preferredSize: const Size(200, double.infinity),
|
||||
preferredSize: const Size(200, double.infinity),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: model.jobTable.isEmpty
|
||||
? Center(child: Text(translate("Empty")))
|
||||
: Container(
|
||||
margin:
|
||||
const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Obx(
|
||||
() => ListView.builder(
|
||||
controller: ScrollController(),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = model.jobTable[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 5),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(15.0),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Transform.rotate(
|
||||
angle: item.isRemote ? pi : 0,
|
||||
child: SvgPicture.asset(
|
||||
"assets/arrow.svg",
|
||||
color: Theme.of(context)
|
||||
.tabBarTheme
|
||||
.labelColor,
|
||||
),
|
||||
).paddingOnly(left: 15),
|
||||
const SizedBox(
|
||||
width: 16.0,
|
||||
? generateCard(
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/transfer.svg",
|
||||
color: Theme.of(context).tabBarTheme.labelColor,
|
||||
height: 40,
|
||||
).paddingOnly(bottom: 10),
|
||||
Text(
|
||||
translate("No transfers in progress"),
|
||||
textAlign: TextAlign.center,
|
||||
textScaleFactor: 1.20,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).tabBarTheme.labelColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Obx(
|
||||
() => ListView.builder(
|
||||
controller: ScrollController(),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = model.jobTable[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 5),
|
||||
child: generateCard(
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Transform.rotate(
|
||||
angle: item.isRemote ? pi : 0,
|
||||
child: SvgPicture.asset(
|
||||
"assets/arrow.svg",
|
||||
color: Theme.of(context)
|
||||
.tabBarTheme
|
||||
.labelColor,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Tooltip(
|
||||
waitDuration:
|
||||
Duration(milliseconds: 500),
|
||||
message: item.jobName,
|
||||
child: Text(
|
||||
item.jobName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).paddingSymmetric(vertical: 10),
|
||||
).paddingOnly(left: 15),
|
||||
const SizedBox(
|
||||
width: 16.0,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Tooltip(
|
||||
waitDuration:
|
||||
Duration(milliseconds: 500),
|
||||
message: item.jobName,
|
||||
child: Text(
|
||||
item.jobName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).paddingSymmetric(vertical: 10),
|
||||
),
|
||||
Text(
|
||||
'${translate("Total")} ${readableFileSize(item.totalSize.toDouble())}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: MyTheme.darkGray,
|
||||
),
|
||||
Text(
|
||||
'${translate("Total")} ${readableFileSize(item.totalSize.toDouble())}',
|
||||
),
|
||||
Offstage(
|
||||
offstage:
|
||||
item.state != JobState.inProgress,
|
||||
child: Text(
|
||||
'${translate("Speed")} ${readableFileSize(item.speed)}/s',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: MyTheme.darkGray,
|
||||
),
|
||||
),
|
||||
Offstage(
|
||||
offstage:
|
||||
item.state != JobState.inProgress,
|
||||
child: Text(
|
||||
'${translate("Speed")} ${readableFileSize(item.speed)}/s',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: MyTheme.darkGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
Offstage(
|
||||
offstage:
|
||||
item.state == JobState.inProgress,
|
||||
child: Text(
|
||||
translate(
|
||||
item.display(),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: MyTheme.darkGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
Offstage(
|
||||
offstage:
|
||||
item.state != JobState.inProgress,
|
||||
child: LinearPercentIndicator(
|
||||
padding: EdgeInsets.only(right: 15),
|
||||
animateFromLastPercent: true,
|
||||
center: Text(
|
||||
'${(item.finishedSize / item.totalSize * 100).toStringAsFixed(0)}%',
|
||||
),
|
||||
barRadius: Radius.circular(15),
|
||||
percent: item.finishedSize /
|
||||
item.totalSize,
|
||||
progressColor: MyTheme.accent,
|
||||
backgroundColor:
|
||||
Theme.of(context).hoverColor,
|
||||
lineHeight:
|
||||
kDesktopFileTransferRowHeight,
|
||||
).paddingSymmetric(vertical: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
),
|
||||
Offstage(
|
||||
offstage: item.state != JobState.paused,
|
||||
child: MenuButton(
|
||||
onPressed: () {
|
||||
model.resumeJob(item.id);
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
"assets/refresh.svg",
|
||||
color: Colors.white,
|
||||
offstage:
|
||||
item.state == JobState.inProgress,
|
||||
child: Text(
|
||||
translate(
|
||||
item.display(),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: MyTheme.darkGray,
|
||||
),
|
||||
color: MyTheme.accent,
|
||||
hoverColor: MyTheme.accent80,
|
||||
),
|
||||
),
|
||||
MenuButton(
|
||||
padding: EdgeInsets.only(right: 15),
|
||||
child: SvgPicture.asset(
|
||||
"assets/close.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
model.jobTable.removeAt(index);
|
||||
model.cancelJob(item.id);
|
||||
},
|
||||
color: MyTheme.accent,
|
||||
hoverColor: MyTheme.accent80,
|
||||
Offstage(
|
||||
offstage:
|
||||
item.state != JobState.inProgress,
|
||||
child: LinearPercentIndicator(
|
||||
padding: EdgeInsets.only(right: 15),
|
||||
animateFromLastPercent: true,
|
||||
center: Text(
|
||||
'${(item.finishedSize / item.totalSize * 100).toStringAsFixed(0)}%',
|
||||
),
|
||||
barRadius: Radius.circular(15),
|
||||
percent: item.finishedSize /
|
||||
item.totalSize,
|
||||
progressColor: MyTheme.accent,
|
||||
backgroundColor:
|
||||
Theme.of(context).hoverColor,
|
||||
lineHeight:
|
||||
kDesktopFileTransferRowHeight,
|
||||
).paddingSymmetric(vertical: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).paddingSymmetric(vertical: 10),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: model.jobTable.length,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Offstage(
|
||||
offstage: item.state != JobState.paused,
|
||||
child: MenuButton(
|
||||
onPressed: () {
|
||||
model.resumeJob(item.id);
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
"assets/refresh.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
color: MyTheme.accent,
|
||||
hoverColor: MyTheme.accent80,
|
||||
),
|
||||
),
|
||||
MenuButton(
|
||||
padding: EdgeInsets.only(right: 15),
|
||||
child: SvgPicture.asset(
|
||||
"assets/close.svg",
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
model.jobTable.removeAt(index);
|
||||
model.cancelJob(item.id);
|
||||
},
|
||||
color: MyTheme.accent,
|
||||
hoverColor: MyTheme.accent80,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).paddingSymmetric(vertical: 10),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: model.jobTable.length,
|
||||
),
|
||||
));
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget headTools(bool isLocal) {
|
||||
@@ -1028,7 +1054,9 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
color: selectedItems.length == 0
|
||||
? MyTheme.darkGray
|
||||
? Theme.of(context).brightness == Brightness.light
|
||||
? MyTheme.grayBg
|
||||
: MyTheme.darkGray
|
||||
: Colors.white,
|
||||
),
|
||||
)
|
||||
@@ -1037,7 +1065,9 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
child: SvgPicture.asset(
|
||||
"assets/arrow.svg",
|
||||
color: selectedItems.length == 0
|
||||
? MyTheme.darkGray
|
||||
? Theme.of(context).brightness == Brightness.light
|
||||
? MyTheme.grayBg
|
||||
: MyTheme.darkGray
|
||||
: Colors.white,
|
||||
alignment: Alignment.bottomRight,
|
||||
),
|
||||
@@ -1046,14 +1076,18 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
? SvgPicture.asset(
|
||||
"assets/arrow.svg",
|
||||
color: selectedItems.length == 0
|
||||
? MyTheme.darkGray
|
||||
? Theme.of(context).brightness == Brightness.light
|
||||
? MyTheme.grayBg
|
||||
: MyTheme.darkGray
|
||||
: Colors.white,
|
||||
)
|
||||
: Text(
|
||||
translate('Receive'),
|
||||
style: TextStyle(
|
||||
color: selectedItems.length == 0
|
||||
? MyTheme.darkGray
|
||||
? Theme.of(context).brightness == Brightness.light
|
||||
? MyTheme.grayBg
|
||||
: MyTheme.darkGray
|
||||
: Colors.white,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -98,7 +98,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
labelGetter: DesktopTab.labelGetterAlias,
|
||||
)),
|
||||
);
|
||||
return Platform.isMacOS
|
||||
return Platform.isMacOS || kUseCompatibleUiMode
|
||||
? tabWidget
|
||||
: SubWindowDragToResizeArea(
|
||||
child: tabWidget,
|
||||
|
||||
@@ -46,15 +46,19 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
|
||||
final double em = 13;
|
||||
final btnFontSize = 0.9 * em;
|
||||
final double button_radius = 6;
|
||||
final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark;
|
||||
final buttonStyle = OutlinedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(button_radius)),
|
||||
));
|
||||
final inputBorder = OutlineInputBorder(
|
||||
borderRadius: BorderRadius.zero,
|
||||
borderSide: BorderSide(color: Colors.black12));
|
||||
borderSide:
|
||||
BorderSide(color: isDarkTheme ? Colors.white70 : Colors.black12));
|
||||
final textColor = isDarkTheme ? null : Colors.black87;
|
||||
final dividerColor = isDarkTheme ? Colors.white70 : Colors.black87;
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
backgroundColor: null,
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -91,8 +95,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
|
||||
style: buttonStyle,
|
||||
child: Text(translate('Change Path'),
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: btnFontSize)))
|
||||
color: textColor, fontSize: btnFontSize)))
|
||||
.marginOnly(left: em))
|
||||
],
|
||||
).marginSymmetric(vertical: 2 * em),
|
||||
@@ -127,8 +130,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
|
||||
)).marginOnly(top: 2 * em),
|
||||
Row(children: [Text(translate('agreement_tip'))])
|
||||
.marginOnly(top: em),
|
||||
Divider(color: Colors.black87)
|
||||
.marginSymmetric(vertical: 0.5 * em),
|
||||
Divider(color: dividerColor).marginSymmetric(vertical: 0.5 * em),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -143,8 +145,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
|
||||
style: buttonStyle,
|
||||
child: Text(translate('Cancel'),
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: btnFontSize)))
|
||||
color: textColor, fontSize: btnFontSize)))
|
||||
.marginOnly(right: 2 * em)),
|
||||
Obx(() => ElevatedButton(
|
||||
onPressed: btnEnabled.value ? install : null,
|
||||
@@ -167,8 +168,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
|
||||
style: buttonStyle,
|
||||
child: Text(translate('Run without install'),
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: btnFontSize)))
|
||||
color: textColor, fontSize: btnFontSize)))
|
||||
.marginOnly(left: 2 * em)),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -107,13 +107,15 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
||||
labelGetter: DesktopTab.labelGetterAlias,
|
||||
)),
|
||||
);
|
||||
return Platform.isMacOS
|
||||
return Platform.isMacOS || kUseCompatibleUiMode
|
||||
? tabWidget
|
||||
: SubWindowDragToResizeArea(
|
||||
child: tabWidget,
|
||||
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
|
||||
windowId: stateGlobal.windowId,
|
||||
);
|
||||
: Obx(
|
||||
() => SubWindowDragToResizeArea(
|
||||
child: tabWidget,
|
||||
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
|
||||
windowId: stateGlobal.windowId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onRemoveId(String id) {
|
||||
|
||||
@@ -205,11 +205,13 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
),
|
||||
),
|
||||
);
|
||||
return Platform.isMacOS
|
||||
return Platform.isMacOS || kUseCompatibleUiMode
|
||||
? tabWidget
|
||||
: Obx(() => SubWindowDragToResizeArea(
|
||||
key: contentKey,
|
||||
child: tabWidget,
|
||||
// Specially configured for a better resize area and remote control.
|
||||
childPadding: kDragToResizeAreaPadding,
|
||||
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
|
||||
windowId: stateGlobal.windowId,
|
||||
));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/remote_tab_page.dart';
|
||||
@@ -26,6 +28,9 @@ class DesktopRemoteScreen extends StatelessWidget {
|
||||
ChangeNotifierProvider.value(value: gFFI.canvasModel),
|
||||
],
|
||||
child: Scaffold(
|
||||
// Set transparent background for padding the resize area out of the flutter view.
|
||||
// This allows the wallpaper goes through our resize area. (Linux only now).
|
||||
backgroundColor: Platform.isLinux ? Colors.transparent : null,
|
||||
body: ConnectionTabPage(
|
||||
params: params,
|
||||
),
|
||||
|
||||
@@ -37,7 +37,7 @@ class _MenuButtonState extends State<MenuButton> {
|
||||
message: widget.tooltip,
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Ink(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(_borderRadius),
|
||||
color: _isHover ? widget.hoverColor : widget.color,
|
||||
|
||||
@@ -23,6 +23,10 @@ import '../../common/shared_state.dart';
|
||||
import './popup_menu.dart';
|
||||
import './kb_layout_type_chooser.dart';
|
||||
|
||||
const _kKeyLegacyMode = 'legacy';
|
||||
const _kKeyMapMode = 'map';
|
||||
const _kKeyTranslateMode = 'translate';
|
||||
|
||||
class MenubarState {
|
||||
final kStoreKey = 'remoteMenubarState';
|
||||
late RxBool show;
|
||||
@@ -598,6 +602,7 @@ class _ControlMenu extends StatelessWidget {
|
||||
hoverColor: _MenubarTheme.hoverBlueColor,
|
||||
ffi: ffi,
|
||||
menuChildren: [
|
||||
requestElevation(),
|
||||
osPassword(),
|
||||
transferFile(context),
|
||||
tcpTunneling(context),
|
||||
@@ -605,12 +610,22 @@ class _ControlMenu extends StatelessWidget {
|
||||
Divider(),
|
||||
ctrlAltDel(),
|
||||
restart(),
|
||||
insertLock(),
|
||||
blockUserInput(),
|
||||
switchSides(),
|
||||
refresh(),
|
||||
]);
|
||||
}
|
||||
|
||||
requestElevation() {
|
||||
final visible = ffi.elevationModel.showRequestMenu;
|
||||
if (!visible) return Offstage();
|
||||
return _MenuItemButton(
|
||||
child: Text(translate('Request Elevation')),
|
||||
ffi: ffi,
|
||||
onPressed: () => showRequestElevationDialog(id, ffi.dialogManager));
|
||||
}
|
||||
|
||||
osPassword() {
|
||||
return _MenuItemButton(
|
||||
child: Text(translate('OS Password')),
|
||||
@@ -779,6 +794,16 @@ class _ControlMenu extends StatelessWidget {
|
||||
onPressed: () => showRestartRemoteDevice(pi, id, ffi.dialogManager));
|
||||
}
|
||||
|
||||
insertLock() {
|
||||
final perms = ffi.ffiModel.permissions;
|
||||
final visible = perms['keyboard'] != false;
|
||||
if (!visible) return Offstage();
|
||||
return _MenuItemButton(
|
||||
child: Text(translate('Insert Lock')),
|
||||
ffi: ffi,
|
||||
onPressed: () => bind.sessionLockScreen(id: id));
|
||||
}
|
||||
|
||||
blockUserInput() {
|
||||
final perms = ffi.ffiModel.permissions;
|
||||
final pi = ffi.ffiModel.pi;
|
||||
@@ -1092,7 +1117,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
await bind.sessionSetImageQuality(id: widget.id, value: value);
|
||||
}
|
||||
|
||||
return SubmenuButton(
|
||||
return _SubmenuButton(
|
||||
ffi: widget.ffi,
|
||||
child: Text(translate('Image Quality')),
|
||||
menuChildren: [
|
||||
_RadioMenuButton<String>(
|
||||
@@ -1126,7 +1152,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
},
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
].map((e) => _buildPointerTrackWidget(e, widget.ffi)).toList(),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1301,7 +1327,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
bind.sessionChangePreferCodec(id: widget.id);
|
||||
}
|
||||
|
||||
return SubmenuButton(
|
||||
return _SubmenuButton(
|
||||
ffi: widget.ffi,
|
||||
child: Text(translate('Codec')),
|
||||
menuChildren: [
|
||||
_RadioMenuButton<String>(
|
||||
@@ -1332,7 +1359,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
onChanged: onChanged,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
].map((e) => _buildPointerTrackWidget(e, widget.ffi)).toList());
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1364,7 +1391,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
}
|
||||
}
|
||||
|
||||
return SubmenuButton(
|
||||
return _SubmenuButton(
|
||||
ffi: widget.ffi,
|
||||
menuChildren: resolutions
|
||||
.map((e) => _RadioMenuButton(
|
||||
value: '${e.width}x${e.height}',
|
||||
@@ -1372,8 +1400,6 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
onChanged: onChanged,
|
||||
ffi: widget.ffi,
|
||||
child: Text('${e.width}x${e.height}')))
|
||||
.toList()
|
||||
.map((e) => _buildPointerTrackWidget(e, widget.ffi))
|
||||
.toList(),
|
||||
child: Text(translate("Resolution")));
|
||||
}
|
||||
@@ -1534,11 +1560,16 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var ffiModel = Provider.of<FfiModel>(context);
|
||||
if (ffiModel.permissions['keyboard'] == false) return Offstage();
|
||||
// Do not support peer 1.1.9.
|
||||
// Do not check permission here?
|
||||
// var ffiModel = Provider.of<FfiModel>(context);
|
||||
// if (ffiModel.permissions['keyboard'] == false) return Offstage();
|
||||
if (stateGlobal.grabKeyboard) {
|
||||
bind.sessionSetKeyboardMode(id: id, value: 'map');
|
||||
if (bind.sessionIsKeyboardModeSupported(id: id, mode: _kKeyMapMode)) {
|
||||
bind.sessionSetKeyboardMode(id: id, value: _kKeyMapMode);
|
||||
} else if (bind.sessionIsKeyboardModeSupported(
|
||||
id: id, mode: _kKeyLegacyMode)) {
|
||||
bind.sessionSetKeyboardMode(id: id, value: _kKeyLegacyMode);
|
||||
}
|
||||
return Offstage();
|
||||
}
|
||||
return _IconSubmenuButton(
|
||||
@@ -1551,13 +1582,13 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
|
||||
mode() {
|
||||
return futureBuilder(future: () async {
|
||||
return await bind.sessionGetKeyboardMode(id: id) ?? 'legacy';
|
||||
return await bind.sessionGetKeyboardMode(id: id) ?? _kKeyLegacyMode;
|
||||
}(), hasData: (data) {
|
||||
final groupValue = data as String;
|
||||
List<KeyboardModeMenu> modes = [
|
||||
KeyboardModeMenu(key: 'legacy', menu: 'Legacy mode'),
|
||||
KeyboardModeMenu(key: 'map', menu: 'Map mode'),
|
||||
KeyboardModeMenu(key: 'translate', menu: 'Translate mode'),
|
||||
KeyboardModeMenu(key: _kKeyLegacyMode, menu: 'Legacy mode'),
|
||||
KeyboardModeMenu(key: _kKeyMapMode, menu: 'Map mode'),
|
||||
KeyboardModeMenu(key: _kKeyTranslateMode, menu: 'Translate mode'),
|
||||
];
|
||||
List<_RadioMenuButton> list = [];
|
||||
onChanged(String? value) async {
|
||||
@@ -1567,13 +1598,13 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
|
||||
for (KeyboardModeMenu mode in modes) {
|
||||
if (bind.sessionIsKeyboardModeSupported(id: id, mode: mode.key)) {
|
||||
if (mode.key == 'translate') {
|
||||
if (mode.key == _kKeyTranslateMode) {
|
||||
if (Platform.isLinux || pi.platform == kPeerPlatformLinux) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var text = translate(mode.menu);
|
||||
if (mode.key == 'translate') {
|
||||
if (mode.key == _kKeyTranslateMode) {
|
||||
text = '$text beta';
|
||||
}
|
||||
list.add(_RadioMenuButton<String>(
|
||||
@@ -1877,6 +1908,28 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
|
||||
}
|
||||
}
|
||||
|
||||
class _SubmenuButton extends StatelessWidget {
|
||||
final List<Widget> menuChildren;
|
||||
final Widget? child;
|
||||
final FFI ffi;
|
||||
const _SubmenuButton({
|
||||
Key? key,
|
||||
required this.menuChildren,
|
||||
required this.child,
|
||||
required this.ffi,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SubmenuButton(
|
||||
key: key,
|
||||
child: child,
|
||||
menuChildren:
|
||||
menuChildren.map((e) => _buildPointerTrackWidget(e, ffi)).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MenuItemButton extends StatelessWidget {
|
||||
final VoidCallback? onPressed;
|
||||
final Widget? trailingIcon;
|
||||
|
||||
@@ -523,12 +523,18 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _setMaximize(bool maximize) {
|
||||
stateGlobal.setMaximize(maximize);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowMaximize() {
|
||||
// catch maximize from system
|
||||
if (!widget.isMaximized.value) {
|
||||
widget.isMaximized.value = true;
|
||||
}
|
||||
_setMaximize(true);
|
||||
super.onWindowMaximize();
|
||||
}
|
||||
|
||||
@@ -538,6 +544,7 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
if (widget.isMaximized.value) {
|
||||
widget.isMaximized.value = false;
|
||||
}
|
||||
_setMaximize(false);
|
||||
super.onWindowUnmaximize();
|
||||
}
|
||||
|
||||
|
||||
@@ -291,7 +291,7 @@ void _runApp(
|
||||
void runInstallPage() async {
|
||||
await windowManager.ensureInitialized();
|
||||
await initEnv(kAppTypeMain);
|
||||
_runApp('', const InstallPage(), ThemeMode.light);
|
||||
_runApp('', const InstallPage(), MyTheme.currentThemeMode());
|
||||
windowManager.waitUntilReadyToShow(
|
||||
WindowOptions(size: Size(800, 600), center: true), () async {
|
||||
windowManager.show();
|
||||
|
||||
@@ -374,8 +374,7 @@ void showWaitUacDialog(
|
||||
));
|
||||
}
|
||||
|
||||
void _showRequestElevationDialog(
|
||||
String id, OverlayDialogManager dialogManager) {
|
||||
void showRequestElevationDialog(String id, OverlayDialogManager dialogManager) {
|
||||
RxString groupValue = ''.obs;
|
||||
RxString errUser = ''.obs;
|
||||
RxString errPwd = ''.obs;
|
||||
@@ -531,7 +530,7 @@ void showOnBlockDialog(
|
||||
dialogManager.show(tag: '$id-$type', (setState, close) {
|
||||
void submit() {
|
||||
close();
|
||||
_showRequestElevationDialog(id, dialogManager);
|
||||
showRequestElevationDialog(id, dialogManager);
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
@@ -553,7 +552,7 @@ void showElevationError(String id, String type, String title, String text,
|
||||
dialogManager.show(tag: '$id-$type', (setState, close) {
|
||||
void submit() {
|
||||
close();
|
||||
_showRequestElevationDialog(id, dialogManager);
|
||||
showRequestElevationDialog(id, dialogManager);
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
|
||||
@@ -459,17 +459,22 @@ class InputModel {
|
||||
}
|
||||
evt['type'] = type;
|
||||
if (isDesktop) {
|
||||
y = y - stateGlobal.tabBarHeight;
|
||||
y = y - stateGlobal.tabBarHeight - stateGlobal.windowBorderWidth.value;
|
||||
x -= stateGlobal.windowBorderWidth.value;
|
||||
}
|
||||
final canvasModel = parent.target!.canvasModel;
|
||||
final nearThr = 3;
|
||||
var nearRight = (canvasModel.size.width - x) < nearThr;
|
||||
var nearBottom = (canvasModel.size.height - y) < nearThr;
|
||||
|
||||
final ffiModel = parent.target!.ffiModel;
|
||||
if (isMove) {
|
||||
canvasModel.moveDesktopMouse(x, y);
|
||||
}
|
||||
final d = ffiModel.display;
|
||||
final imageWidth = d.width * canvasModel.scale;
|
||||
final imageHeight = d.height * canvasModel.scale;
|
||||
if (canvasModel.scrollStyle == ScrollStyle.scrollbar) {
|
||||
final imageWidth = d.width * canvasModel.scale;
|
||||
final imageHeight = d.height * canvasModel.scale;
|
||||
x += imageWidth * canvasModel.scrollX;
|
||||
y += imageHeight * canvasModel.scrollY;
|
||||
|
||||
@@ -487,10 +492,32 @@ class InputModel {
|
||||
|
||||
x /= canvasModel.scale;
|
||||
y /= canvasModel.scale;
|
||||
if (canvasModel.scale > 0 && canvasModel.scale < 1) {
|
||||
final step = 1.0 / canvasModel.scale - 1;
|
||||
if (nearRight) {
|
||||
x += step;
|
||||
}
|
||||
if (nearBottom) {
|
||||
y += step;
|
||||
}
|
||||
}
|
||||
x += d.x;
|
||||
y += d.y;
|
||||
var evtX = 0;
|
||||
var evtY = 0;
|
||||
try {
|
||||
evtX = x.round();
|
||||
evtY = y.round();
|
||||
} catch (e) {
|
||||
debugPrintStack(
|
||||
label: 'canvasModel.scale value ${canvasModel.scale}, $e');
|
||||
return;
|
||||
}
|
||||
|
||||
if (x < d.x || y < d.y || x > (d.x + d.width) || y > (d.y + d.height)) {
|
||||
if (evtX < d.x ||
|
||||
evtY < d.y ||
|
||||
evtX > (d.x + d.width) ||
|
||||
evtY > (d.y + d.height)) {
|
||||
// If left mouse up, no early return.
|
||||
if (evt['buttons'] != kPrimaryMouseButton || type != 'up') {
|
||||
return;
|
||||
@@ -498,12 +525,12 @@ class InputModel {
|
||||
}
|
||||
|
||||
if (type != '') {
|
||||
x = 0;
|
||||
y = 0;
|
||||
evtX = 0;
|
||||
evtY = 0;
|
||||
}
|
||||
|
||||
evt['x'] = '${x.round()}';
|
||||
evt['y'] = '${y.round()}';
|
||||
evt['x'] = '$evtX';
|
||||
evt['y'] = '$evtY';
|
||||
var buttons = '';
|
||||
switch (evt['buttons']) {
|
||||
case kPrimaryMouseButton:
|
||||
|
||||
@@ -156,7 +156,7 @@ class FfiModel with ChangeNotifier {
|
||||
} else if (name == 'clipboard') {
|
||||
Clipboard.setData(ClipboardData(text: evt['content']));
|
||||
} else if (name == 'permission') {
|
||||
parent.target?.ffiModel.updatePermission(evt, peerId);
|
||||
updatePermission(evt, peerId);
|
||||
} else if (name == 'chat_client_mode') {
|
||||
parent.target?.chatModel
|
||||
.receive(ChatModel.clientModeID, evt['text'] ?? '');
|
||||
@@ -203,6 +203,8 @@ class FfiModel with ChangeNotifier {
|
||||
final peer_id = evt['peer_id'].toString();
|
||||
await bind.sessionSwitchSides(id: peer_id);
|
||||
closeConnection(id: peer_id);
|
||||
} else if (name == 'portable_service_running') {
|
||||
parent.target?.elevationModel.onPortableServiceRunning(evt);
|
||||
} else if (name == "on_url_scheme_received") {
|
||||
final url = evt['url'].toString();
|
||||
parseRustdeskUri(url);
|
||||
@@ -239,36 +241,33 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
handleSwitchDisplay(Map<String, dynamic> evt, String peerId) {
|
||||
final oldOrientation = _display.width > _display.height;
|
||||
var old = _pi.currentDisplay;
|
||||
_pi.currentDisplay = int.parse(evt['display']);
|
||||
_display.x = double.parse(evt['x']);
|
||||
_display.y = double.parse(evt['y']);
|
||||
_display.width = int.parse(evt['width']);
|
||||
_display.height = int.parse(evt['height']);
|
||||
_display.cursorEmbedded = int.parse(evt['cursor_embedded']) == 1;
|
||||
if (old != _pi.currentDisplay) {
|
||||
parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y);
|
||||
_updateCurDisplay(String peerId, Display newDisplay) {
|
||||
if (newDisplay != _display) {
|
||||
if (newDisplay.x != _display.x || newDisplay.y != _display.y) {
|
||||
parent.target?.cursorModel
|
||||
.updateDisplayOrigin(newDisplay.x, newDisplay.y);
|
||||
}
|
||||
_display = newDisplay;
|
||||
_updateSessionWidthHeight(peerId);
|
||||
}
|
||||
}
|
||||
|
||||
_updateSessionWidthHeight(peerId, display.width, display.height);
|
||||
handleSwitchDisplay(Map<String, dynamic> evt, String peerId) {
|
||||
_pi.currentDisplay = int.parse(evt['display']);
|
||||
var newDisplay = Display();
|
||||
newDisplay.x = double.parse(evt['x']);
|
||||
newDisplay.y = double.parse(evt['y']);
|
||||
newDisplay.width = int.parse(evt['width']);
|
||||
newDisplay.height = int.parse(evt['height']);
|
||||
newDisplay.cursorEmbedded = int.parse(evt['cursor_embedded']) == 1;
|
||||
|
||||
_updateCurDisplay(peerId, newDisplay);
|
||||
|
||||
try {
|
||||
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
|
||||
// remote is mobile, and orientation changed
|
||||
if ((_display.width > _display.height) != oldOrientation) {
|
||||
gFFI.canvasModel.updateViewStyle();
|
||||
}
|
||||
if (_pi.platform == kPeerPlatformLinux ||
|
||||
_pi.platform == kPeerPlatformWindows ||
|
||||
_pi.platform == kPeerPlatformMacOS) {
|
||||
parent.target?.canvasModel.updateViewStyle();
|
||||
}
|
||||
parent.target?.recordingModel.onSwitchDisplay();
|
||||
handleResolutions(peerId, evt["resolutions"]);
|
||||
notifyListeners();
|
||||
@@ -370,7 +369,8 @@ class FfiModel with ChangeNotifier {
|
||||
});
|
||||
}
|
||||
|
||||
_updateSessionWidthHeight(String id, int width, int height) {
|
||||
_updateSessionWidthHeight(String id) {
|
||||
parent.target?.canvasModel.updateViewStyle();
|
||||
bind.sessionSetSize(id: id, width: display.width, height: display.height);
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ class FfiModel with ChangeNotifier {
|
||||
stateGlobal.displaysCount.value = _pi.displays.length;
|
||||
if (_pi.currentDisplay < _pi.displays.length) {
|
||||
_display = _pi.displays[_pi.currentDisplay];
|
||||
_updateSessionWidthHeight(peerId, display.width, display.height);
|
||||
_updateSessionWidthHeight(peerId);
|
||||
}
|
||||
if (displays.isNotEmpty) {
|
||||
parent.target?.dialogManager.showLoading(
|
||||
@@ -439,6 +439,7 @@ class FfiModel with ChangeNotifier {
|
||||
Map<String, dynamic> features = json.decode(evt['features']);
|
||||
_pi.features.privacyMode = features['privacy_mode'] == 1;
|
||||
handleResolutions(peerId, evt["resolutions"]);
|
||||
parent.target?.elevationModel.onPeerInfo(_pi);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -485,7 +486,7 @@ class FfiModel with ChangeNotifier {
|
||||
_pi.displays = newDisplays;
|
||||
stateGlobal.displaysCount.value = _pi.displays.length;
|
||||
if (_pi.currentDisplay >= 0 && _pi.currentDisplay < _pi.displays.length) {
|
||||
_display = _pi.displays[_pi.currentDisplay];
|
||||
_updateCurDisplay(peerId, _pi.displays[_pi.currentDisplay]);
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
@@ -616,13 +617,28 @@ class ViewStyle {
|
||||
final int displayWidth;
|
||||
final int displayHeight;
|
||||
ViewStyle({
|
||||
this.style = '',
|
||||
this.width = 0.0,
|
||||
this.height = 0.0,
|
||||
this.displayWidth = 0,
|
||||
this.displayHeight = 0,
|
||||
required this.style,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.displayWidth,
|
||||
required this.displayHeight,
|
||||
});
|
||||
|
||||
static defaultViewStyle() {
|
||||
final desktop = (isDesktop || isWebDesktop);
|
||||
final w =
|
||||
desktop ? kDesktopDefaultDisplayWidth : kMobileDefaultDisplayWidth;
|
||||
final h =
|
||||
desktop ? kDesktopDefaultDisplayHeight : kMobileDefaultDisplayHeight;
|
||||
return ViewStyle(
|
||||
style: '',
|
||||
width: w.toDouble(),
|
||||
height: h.toDouble(),
|
||||
displayWidth: w,
|
||||
displayHeight: h,
|
||||
);
|
||||
}
|
||||
|
||||
static int _double2Int(double v) => (v * 100).round().toInt();
|
||||
|
||||
@override
|
||||
@@ -651,9 +667,14 @@ class ViewStyle {
|
||||
double get scale {
|
||||
double s = 1.0;
|
||||
if (style == kRemoteViewStyleAdaptive) {
|
||||
final s1 = width / displayWidth;
|
||||
final s2 = height / displayHeight;
|
||||
s = s1 < s2 ? s1 : s2;
|
||||
if (width != 0 &&
|
||||
height != 0 &&
|
||||
displayWidth != 0 &&
|
||||
displayHeight != 0) {
|
||||
final s1 = width / displayWidth;
|
||||
final s2 = height / displayHeight;
|
||||
s = s1 < s2 ? s1 : s2;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -679,7 +700,7 @@ class CanvasModel with ChangeNotifier {
|
||||
// scroll offset y percent
|
||||
double _scrollY = 0.0;
|
||||
ScrollStyle _scrollStyle = ScrollStyle.scrollauto;
|
||||
ViewStyle _lastViewStyle = ViewStyle();
|
||||
ViewStyle _lastViewStyle = ViewStyle.defaultViewStyle();
|
||||
|
||||
final _imageOverflow = false.obs;
|
||||
|
||||
@@ -710,8 +731,15 @@ class CanvasModel with ChangeNotifier {
|
||||
Size getSize() {
|
||||
final size = MediaQueryData.fromWindow(ui.window).size;
|
||||
// If minimized, w or h may be negative here.
|
||||
double w = size.width - windowBorderWidth * 2;
|
||||
double h = size.height - tabBarHeight - windowBorderWidth * 2;
|
||||
double w = size.width -
|
||||
windowBorderWidth * 2 -
|
||||
kDragToResizeAreaPadding.left -
|
||||
kDragToResizeAreaPadding.right;
|
||||
double h = size.height -
|
||||
tabBarHeight -
|
||||
windowBorderWidth * 2 -
|
||||
kDragToResizeAreaPadding.top -
|
||||
kDragToResizeAreaPadding.bottom;
|
||||
return Size(w < 0 ? 0 : w, h < 0 ? 0 : h);
|
||||
}
|
||||
|
||||
@@ -789,17 +817,29 @@ class CanvasModel with ChangeNotifier {
|
||||
double get tabBarHeight => stateGlobal.tabBarHeight;
|
||||
|
||||
moveDesktopMouse(double x, double y) {
|
||||
if (size.width == 0 || size.height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// On mobile platforms, move the canvas with the cursor.
|
||||
final dw = getDisplayWidth() * _scale;
|
||||
final dh = getDisplayHeight() * _scale;
|
||||
var dxOffset = 0;
|
||||
var dyOffset = 0;
|
||||
if (dw > size.width) {
|
||||
dxOffset = (x - dw * (x / size.width) - _x).toInt();
|
||||
}
|
||||
if (dh > size.height) {
|
||||
dyOffset = (y - dh * (y / size.height) - _y).toInt();
|
||||
try {
|
||||
if (dw > size.width) {
|
||||
dxOffset = (x - dw * (x / size.width) - _x).toInt();
|
||||
}
|
||||
if (dh > size.height) {
|
||||
dyOffset = (y - dh * (y / size.height) - _y).toInt();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrintStack(
|
||||
label:
|
||||
'(x,y) ($x,$y), (_x,_y) ($_x,$_y), _scale $_scale, display size (${getDisplayWidth()},${getDisplayHeight()}), size $size, , $e');
|
||||
return;
|
||||
}
|
||||
|
||||
_x += dxOffset;
|
||||
_y += dyOffset;
|
||||
if (dxOffset != 0 || dyOffset != 0) {
|
||||
@@ -1395,6 +1435,21 @@ class RecordingModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
class ElevationModel with ChangeNotifier {
|
||||
WeakReference<FFI> parent;
|
||||
ElevationModel(this.parent);
|
||||
bool _running = false;
|
||||
bool _canElevate = false;
|
||||
bool get showRequestMenu => _canElevate && !_running;
|
||||
onPeerInfo(PeerInfo pi) {
|
||||
_canElevate = pi.platform == kPeerPlatformWindows && pi.sasEnabled == false;
|
||||
}
|
||||
|
||||
onPortableServiceRunning(Map<String, dynamic> evt) {
|
||||
_running = evt['running'] == 'true';
|
||||
}
|
||||
}
|
||||
|
||||
enum ConnType { defaultConn, fileTransfer, portForward, rdp }
|
||||
|
||||
/// Flutter state manager and data communication with the Rust core.
|
||||
@@ -1420,6 +1475,7 @@ class FFI {
|
||||
late final QualityMonitorModel qualityMonitorModel; // session
|
||||
late final RecordingModel recordingModel; // session
|
||||
late final InputModel inputModel; // session
|
||||
late final ElevationModel elevationModel; // session
|
||||
|
||||
FFI() {
|
||||
imageModel = ImageModel(WeakReference(this));
|
||||
@@ -1436,6 +1492,7 @@ class FFI {
|
||||
qualityMonitorModel = QualityMonitorModel(WeakReference(this));
|
||||
recordingModel = RecordingModel(WeakReference(this));
|
||||
inputModel = InputModel(WeakReference(this));
|
||||
elevationModel = ElevationModel(WeakReference(this));
|
||||
}
|
||||
|
||||
/// Start with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward].
|
||||
@@ -1559,6 +1616,19 @@ class Display {
|
||||
? kDesktopDefaultDisplayHeight
|
||||
: kMobileDefaultDisplayHeight;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is Display &&
|
||||
other.runtimeType == runtimeType &&
|
||||
_innerEqual(other);
|
||||
|
||||
bool _innerEqual(Display other) =>
|
||||
other.x == x &&
|
||||
other.y == y &&
|
||||
other.width == width &&
|
||||
other.height == height &&
|
||||
other.cursorEmbedded == cursorEmbedded;
|
||||
}
|
||||
|
||||
class Resolution {
|
||||
|
||||
@@ -9,8 +9,10 @@ import '../consts.dart';
|
||||
class StateGlobal {
|
||||
int _windowId = -1;
|
||||
bool _fullscreen = false;
|
||||
bool _maximize = false;
|
||||
bool grabKeyboard = false;
|
||||
final RxBool _showTabBar = true.obs;
|
||||
final RxBool _showResizeEdge = true.obs;
|
||||
final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize);
|
||||
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
|
||||
final RxBool showRemoteMenuBar = false.obs;
|
||||
@@ -18,12 +20,20 @@ class StateGlobal {
|
||||
|
||||
int get windowId => _windowId;
|
||||
bool get fullscreen => _fullscreen;
|
||||
bool get maximize => _maximize;
|
||||
double get tabBarHeight => fullscreen ? 0 : kDesktopRemoteTabBarHeight;
|
||||
RxBool get showTabBar => _showTabBar;
|
||||
RxDouble get resizeEdgeSize => _resizeEdgeSize;
|
||||
RxDouble get windowBorderWidth => _windowBorderWidth;
|
||||
|
||||
setWindowId(int id) => _windowId = id;
|
||||
setMaximize(bool v) {
|
||||
if (_maximize != v) {
|
||||
_maximize = v;
|
||||
_resizeEdgeSize.value =
|
||||
_maximize ? kMaximizeEdgeSize : kWindowEdgeSize;
|
||||
}
|
||||
}
|
||||
setFullscreen(bool v) {
|
||||
if (_fullscreen != v) {
|
||||
_fullscreen = v;
|
||||
|
||||
Reference in New Issue
Block a user