Merge pull request #3902 from fufesou/feat/linux_virtual_display

Feat/linux virtual display (headless linux)
This commit is contained in:
RustDesk
2023-04-03 14:28:50 +08:00
committed by GitHub
69 changed files with 2662 additions and 849 deletions

View File

@@ -456,7 +456,7 @@ void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async {
await _connectDialog(
id,
dialogManager,
peerPasswordController: TextEditingController(),
passwordController: TextEditingController(),
);
}
@@ -464,8 +464,8 @@ void enterUserLoginDialog(String id, OverlayDialogManager dialogManager) async {
await _connectDialog(
id,
dialogManager,
usernameController: TextEditingController(),
passwordController: TextEditingController(),
osUsernameController: TextEditingController(),
osPasswordController: TextEditingController(),
);
}
@@ -474,20 +474,27 @@ void enterUserLoginAndPasswordDialog(
await _connectDialog(
id,
dialogManager,
usernameController: TextEditingController(),
osUsernameController: TextEditingController(),
osPasswordController: TextEditingController(),
passwordController: TextEditingController(),
peerPasswordController: TextEditingController(),
);
}
_connectDialog(
String id,
OverlayDialogManager dialogManager, {
TextEditingController? usernameController,
TextEditingController? osUsernameController,
TextEditingController? osPasswordController,
TextEditingController? passwordController,
TextEditingController? peerPasswordController,
}) async {
var remember = await bind.sessionGetRemember(id: id) ?? false;
var rememberPassword = false;
if (passwordController != null) {
rememberPassword = await bind.sessionGetRemember(id: id) ?? false;
}
var rememberAccount = false;
if (osUsernameController != null) {
rememberAccount = await bind.sessionGetRemember(id: id) ?? false;
}
dialogManager.dismissAll();
dialogManager.show((setState, close) {
cancel() {
@@ -496,82 +503,129 @@ _connectDialog(
}
submit() {
// to-do:
// username and password are about remote OS account.
// If the remote side is headless.
// The client side should login to remote OS account, to enable X desktop session.
// `username` and `password` will be used in the near future.
final username = usernameController?.text.trim() ?? '';
final osUsername = osUsernameController?.text.trim() ?? '';
final osPassword = osPasswordController?.text.trim() ?? '';
final password = passwordController?.text.trim() ?? '';
final peerPassword = peerPasswordController?.text.trim() ?? '';
if (peerPasswordController != null && peerPassword.isEmpty) return;
if (passwordController != null && password.isEmpty) return;
if (rememberAccount) {
bind.sessionPeerOption(id: id, name: 'os-username', value: osUsername);
bind.sessionPeerOption(id: id, name: 'os-password', value: osPassword);
}
gFFI.login(
// username,
// password,
osUsername,
osPassword,
id,
peerPassword,
remember,
password,
rememberPassword,
);
close();
dialogManager.showLoading(translate('Logging in...'),
onCancel: closeConnection);
}
descWidget(String text) {
return Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
text,
maxLines: 3,
softWrap: true,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 16),
),
),
Container(
height: 8,
),
],
);
}
rememberWidget(
String desc,
bool remember,
ValueChanged<bool?>? onChanged,
) {
return CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(desc),
value: remember,
onChanged: onChanged,
);
}
osAccountWidget() {
if (osUsernameController == null || osPasswordController == null) {
return Offstage();
}
return Column(
children: [
descWidget(translate('login_linux_tip')),
DialogTextField(
title: translate(DialogTextField.kUsernameTitle),
controller: osUsernameController,
prefixIcon: DialogTextField.kUsernameIcon,
errorText: null,
),
PasswordWidget(
controller: osPasswordController,
autoFocus: false,
),
rememberWidget(
translate('remember_account_tip'),
rememberAccount,
(v) {
if (v != null) {
setState(() => rememberAccount = v);
}
},
),
],
);
}
passwdWidget() {
if (passwordController == null) {
return Offstage();
}
return Column(
children: [
descWidget(translate('verify_rustdesk_password_tip')),
PasswordWidget(
controller: passwordController,
autoFocus: osUsernameController == null,
),
rememberWidget(
translate('Remember password'),
rememberPassword,
(v) {
if (v != null) {
setState(() => rememberPassword = v);
}
},
),
],
);
}
return CustomAlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.password_rounded, color: MyTheme.accent),
(usernameController == null
? Text(translate('Password Required'))
: Tooltip(
message: translate('login_linux_tooltip_tip'),
child: Text(translate('login_linux_tip')),
))
.paddingOnly(left: 10),
Text(translate('Password Required')).paddingOnly(left: 10),
],
),
content: Column(mainAxisSize: MainAxisSize.min, children: [
usernameController == null
osAccountWidget(),
osUsernameController == null || passwordController == null
? Offstage()
: DialogTextField(
title: translate(DialogTextField.kUsernameTitle),
controller: usernameController,
prefixIcon: DialogTextField.kUsernameIcon,
errorText: null,
),
passwordController == null
? Offstage()
: PasswordWidget(
controller: passwordController,
autoFocus: false,
),
usernameController == null || peerPasswordController == null
? Offstage()
: const Divider(),
peerPasswordController == null
? Offstage()
: PasswordWidget(
controller: peerPasswordController,
autoFocus: usernameController == null,
hintText: 'enter_rustdesk_passwd_tip',
),
peerPasswordController == null
? Offstage()
: CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
translate('remember_rustdesk_passwd_tip'),
),
value: remember,
onChanged: (v) {
if (v != null) {
setState(() => remember = v);
}
},
),
: Container(height: 12),
passwdWidget(),
]),
actions: [
dialogButton(
@@ -847,3 +901,149 @@ void showRestartRemoteDevice(
));
if (res == true) bind.sessionRestartRemoteDevice(id: id);
}
showSetOSPassword(
String id,
bool login,
OverlayDialogManager dialogManager,
) async {
final controller = TextEditingController();
var password = await bind.sessionGetOption(id: id, arg: 'os-password') ?? '';
var autoLogin = await bind.sessionGetOption(id: id, arg: 'auto-login') != '';
controller.text = password;
dialogManager.show((setState, close) {
submit() {
var text = controller.text.trim();
bind.sessionPeerOption(id: id, name: 'os-password', value: text);
bind.sessionPeerOption(
id: id, name: 'auto-login', value: autoLogin ? 'Y' : '');
if (text != '' && login) {
bind.sessionInputOsPassword(id: id, value: text);
}
close();
}
return CustomAlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.password_rounded, color: MyTheme.accent),
Text(translate('OS Password')).paddingOnly(left: 10),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
PasswordWidget(controller: controller),
CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
translate('Auto Login'),
),
value: autoLogin,
onChanged: (v) {
if (v == null) return;
setState(() => autoLogin = v);
},
),
],
),
actions: [
dialogButton(
"Cancel",
icon: Icon(Icons.close_rounded),
onPressed: close,
isOutline: true,
),
dialogButton(
"OK",
icon: Icon(Icons.done_rounded),
onPressed: submit,
),
],
onSubmit: submit,
onCancel: close,
);
});
}
showSetOSAccount(
String id,
OverlayDialogManager dialogManager,
) async {
final usernameController = TextEditingController();
final passwdController = TextEditingController();
var username = await bind.sessionGetOption(id: id, arg: 'os-username') ?? '';
var password = await bind.sessionGetOption(id: id, arg: 'os-password') ?? '';
usernameController.text = username;
passwdController.text = password;
dialogManager.show((setState, close) {
submit() {
final username = usernameController.text.trim();
final password = usernameController.text.trim();
bind.sessionPeerOption(id: id, name: 'os-username', value: username);
bind.sessionPeerOption(id: id, name: 'os-password', value: password);
close();
}
descWidget(String text) {
return Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
text,
maxLines: 3,
softWrap: true,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 16),
),
),
Container(
height: 8,
),
],
);
}
return CustomAlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.password_rounded, color: MyTheme.accent),
Text(translate('OS Account')).paddingOnly(left: 10),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
descWidget(translate("os_account_desk_tip")),
DialogTextField(
title: translate(DialogTextField.kUsernameTitle),
controller: usernameController,
prefixIcon: DialogTextField.kUsernameIcon,
errorText: null,
),
PasswordWidget(controller: passwdController),
],
),
actions: [
dialogButton(
"Cancel",
icon: Icon(Icons.close_rounded),
onPressed: close,
isOutline: true,
),
dialogButton(
"OK",
icon: Icon(Icons.done_rounded),
onPressed: submit,
),
],
onSubmit: submit,
onCancel: close,
);
});
}

View File

@@ -225,9 +225,7 @@ class _ConnectionPageState extends State<ConnectionPage>
children: [
Button(
isOutline: true,
onTap: () {
onConnect(isFileTransfer: true);
},
onTap: () => onConnect(isFileTransfer: true),
text: "Transfer File",
),
const SizedBox(

View File

@@ -639,7 +639,7 @@ class _ControlMenu extends StatelessWidget {
ffi: ffi,
menuChildren: [
requestElevation(),
osPassword(),
ffi.ffiModel.pi.is_headless ? osAccount() : osPassword(),
transferFile(context),
tcpTunneling(context),
note(),
@@ -662,78 +662,20 @@ class _ControlMenu extends StatelessWidget {
onPressed: () => showRequestElevationDialog(id, ffi.dialogManager));
}
osAccount() {
return _MenuItemButton(
child: Text(translate('OS Account')),
trailingIcon: Transform.scale(scale: 0.8, child: Icon(Icons.edit)),
ffi: ffi,
onPressed: () => showSetOSAccount(id, ffi.dialogManager));
}
osPassword() {
return _MenuItemButton(
child: Text(translate('OS Password')),
trailingIcon: Transform.scale(scale: 0.8, child: Icon(Icons.edit)),
ffi: ffi,
onPressed: () => _showSetOSPassword(id, false, ffi.dialogManager));
}
_showSetOSPassword(
String id, bool login, OverlayDialogManager dialogManager) async {
final controller = TextEditingController();
var password =
await bind.sessionGetOption(id: id, arg: 'os-password') ?? '';
var autoLogin =
await bind.sessionGetOption(id: id, arg: 'auto-login') != '';
controller.text = password;
dialogManager.show((setState, close) {
submit() {
var text = controller.text.trim();
bind.sessionPeerOption(id: id, name: 'os-password', value: text);
bind.sessionPeerOption(
id: id, name: 'auto-login', value: autoLogin ? 'Y' : '');
if (text != '' && login) {
bind.sessionInputOsPassword(id: id, value: text);
}
close();
}
return CustomAlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.password_rounded, color: MyTheme.accent),
Text(translate('OS Password')).paddingOnly(left: 10),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
PasswordWidget(controller: controller),
CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
translate('Auto Login'),
),
value: autoLogin,
onChanged: (v) {
if (v == null) return;
setState(() => autoLogin = v);
},
),
],
),
actions: [
dialogButton(
"Cancel",
icon: Icon(Icons.close_rounded),
onPressed: close,
isOutline: true,
),
dialogButton(
"OK",
icon: Icon(Icons.done_rounded),
onPressed: submit,
),
],
onSubmit: submit,
onCancel: close,
);
});
onPressed: () => showSetOSPassword(id, false, ffi.dialogManager));
}
transferFile(BuildContext context) {

View File

@@ -548,19 +548,39 @@ class _RemotePageState extends State<RemotePage> {
more.add(PopupMenuItem<String>(
child: Text(translate('Refresh')), value: 'refresh'));
}
more.add(PopupMenuItem<String>(
child: Row(
children: ([
Text(translate('OS Password')),
TextButton(
style: flatButtonStyle,
onPressed: () {
showSetOSPassword(id, false, gFFI.dialogManager);
},
child: Icon(Icons.edit, color: MyTheme.accent),
)
])),
value: 'enter_os_password'));
if (gFFI.ffiModel.pi.is_headless) {
more.add(
PopupMenuItem<String>(
child: Row(
children: ([
Text(translate('OS Account')),
TextButton(
style: flatButtonStyle,
onPressed: () {
showSetOSAccount(id, gFFI.dialogManager);
},
child: Icon(Icons.edit, color: MyTheme.accent),
)
])),
value: 'enter_os_account'),
);
} else {
more.add(
PopupMenuItem<String>(
child: Row(
children: ([
Text(translate('OS Password')),
TextButton(
style: flatButtonStyle,
onPressed: () {
showSetOSPassword(id, false, gFFI.dialogManager);
},
child: Icon(Icons.edit, color: MyTheme.accent),
)
])),
value: 'enter_os_password'),
);
}
if (!isWebDesktop) {
if (perms['keyboard'] != false && perms['clipboard'] != false) {
more.add(PopupMenuItem<String>(
@@ -657,6 +677,8 @@ class _RemotePageState extends State<RemotePage> {
} else {
showSetOSPassword(id, true, gFFI.dialogManager);
}
} else if (value == 'enter_os_account') {
showSetOSAccount(id, gFFI.dialogManager);
} else if (value == 'reset_canvas') {
gFFI.cursorModel.reset();
} else if (value == 'restart') {
@@ -1072,50 +1094,6 @@ void showOptions(
}, clickMaskDismiss: true, backDismiss: true);
}
void showSetOSPassword(
String id, bool login, OverlayDialogManager dialogManager) async {
final controller = TextEditingController();
var password = await bind.sessionGetOption(id: id, arg: "os-password") ?? "";
var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != "";
controller.text = password;
dialogManager.show((setState, close) {
return CustomAlertDialog(
title: Text(translate('OS Password')),
content: Column(mainAxisSize: MainAxisSize.min, children: [
PasswordWidget(controller: controller),
CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
translate('Auto Login'),
),
value: autoLogin,
onChanged: (v) {
if (v == null) return;
setState(() => autoLogin = v);
},
),
]),
actions: [
dialogButton('Cancel', onPressed: close, isOutline: true),
dialogButton(
'OK',
onPressed: () {
var text = controller.text.trim();
bind.sessionPeerOption(id: id, name: "os-password", value: text);
bind.sessionPeerOption(
id: id, name: "auto-login", value: autoLogin ? 'Y' : '');
if (text != "" && login) {
bind.sessionInputOsPassword(id: id, value: text);
}
close();
},
),
]);
});
}
void sendPrompt(bool isMac, String key) {
final old = isMac ? gFFI.inputModel.command : gFFI.inputModel.ctrl;
if (isMac) {

View File

@@ -293,11 +293,11 @@ class FfiModel with ChangeNotifier {
wrongPasswordDialog(id, dialogManager, type, title, text);
} else if (type == 'input-password') {
enterPasswordDialog(id, dialogManager);
} else if (type == 'xsession-login' || type == 'xsession-re-login') {
// to-do
} else if (type == 'xsession-login-password' ||
type == 'xsession-login-password') {
// to-do
} else if (type == 'session-login' || type == 'session-re-login') {
enterUserLoginDialog(id, dialogManager);
} else if (type == 'session-login-password' ||
type == 'session-login-password') {
enterUserLoginAndPasswordDialog(id, dialogManager);
} else if (type == 'restarting') {
showMsgBox(id, type, title, text, link, false, dialogManager,
hasCancel: false);
@@ -1631,8 +1631,14 @@ class FFI {
}
/// Login with [password], choose if the client should [remember] it.
void login(String id, String password, bool remember) {
bind.sessionLogin(id: id, password: password, remember: remember);
void login(String osUsername, String osPassword, String id, String password,
bool remember) {
bind.sessionLogin(
id: id,
osUsername: osUsername,
osPassword: osPassword,
password: password,
remember: remember);
}
/// Close the remote session.
@@ -1719,6 +1725,7 @@ class PeerInfo {
Map<String, dynamic> platform_additions = {};
bool get is_wayland => platform_additions['is_wayland'] == true;
bool get is_headless => platform_additions['headless'] == true;
}
const canvasKey = 'canvas';