flutter_desktop: check remote menu, mid commit
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
f1bbe9ca5e
commit
21b277ea3f
@ -1,7 +1,6 @@
|
|||||||
import 'package:contextmenu/contextmenu.dart';
|
import 'package:contextmenu/contextmenu.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
@ -11,6 +10,7 @@ import '../../models/peer_model.dart';
|
|||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import './material_mod_popup_menu.dart' as mod_menu;
|
import './material_mod_popup_menu.dart' as mod_menu;
|
||||||
import './popup_menu.dart';
|
import './popup_menu.dart';
|
||||||
|
import './utils.dart';
|
||||||
|
|
||||||
class _PopupMenuTheme {
|
class _PopupMenuTheme {
|
||||||
static const Color commonColor = MyTheme.accent;
|
static const Color commonColor = MyTheme.accent;
|
||||||
@ -32,7 +32,7 @@ class _PeerCard extends StatefulWidget {
|
|||||||
final Function(BuildContext, String) connect;
|
final Function(BuildContext, String) connect;
|
||||||
final PopupMenuEntryBuilder popupMenuEntryBuilder;
|
final PopupMenuEntryBuilder popupMenuEntryBuilder;
|
||||||
|
|
||||||
_PeerCard(
|
const _PeerCard(
|
||||||
{required this.peer,
|
{required this.peer,
|
||||||
required this.alias,
|
required this.alias,
|
||||||
required this.connect,
|
required this.connect,
|
||||||
@ -317,7 +317,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
|||||||
return _PeerCard(
|
return _PeerCard(
|
||||||
peer: peer,
|
peer: peer,
|
||||||
alias: alias,
|
alias: alias,
|
||||||
connect: (BuildContext context, String id) => _connect(context, id),
|
connect: (BuildContext context, String id) => connect(context, id),
|
||||||
popupMenuEntryBuilder: _buildPopupMenuEntry,
|
popupMenuEntryBuilder: _buildPopupMenuEntry,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -337,31 +337,6 @@ abstract class BasePeerCard extends StatelessWidget {
|
|||||||
@protected
|
@protected
|
||||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(BuildContext context);
|
Future<List<MenuEntryBase<String>>> _buildMenuItems(BuildContext context);
|
||||||
|
|
||||||
/// Connect to a peer with [id].
|
|
||||||
/// If [isFileTransfer], starts a session only for file transfer.
|
|
||||||
/// If [isTcpTunneling], starts a session only for tcp tunneling.
|
|
||||||
/// If [isRDP], starts a session only for rdp.
|
|
||||||
void _connect(BuildContext context, String id,
|
|
||||||
{bool isFileTransfer = false,
|
|
||||||
bool isTcpTunneling = false,
|
|
||||||
bool isRDP = false}) async {
|
|
||||||
if (id == '') return;
|
|
||||||
id = id.replaceAll(' ', '');
|
|
||||||
assert(!(isFileTransfer && isTcpTunneling && isRDP),
|
|
||||||
"more than one connect type");
|
|
||||||
if (isFileTransfer) {
|
|
||||||
await rustDeskWinManager.newFileTransfer(id);
|
|
||||||
} else if (isTcpTunneling || isRDP) {
|
|
||||||
await rustDeskWinManager.newPortForward(id, isRDP);
|
|
||||||
} else {
|
|
||||||
await rustDeskWinManager.newRemoteDesktop(id);
|
|
||||||
}
|
|
||||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
|
||||||
if (!currentFocus.hasPrimaryFocus) {
|
|
||||||
currentFocus.unfocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuEntryBase<String> _connectCommonAction(
|
MenuEntryBase<String> _connectCommonAction(
|
||||||
BuildContext context, String id, String title,
|
BuildContext context, String id, String title,
|
||||||
{bool isFileTransfer = false,
|
{bool isFileTransfer = false,
|
||||||
@ -373,7 +348,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
|||||||
style: style,
|
style: style,
|
||||||
),
|
),
|
||||||
proc: () {
|
proc: () {
|
||||||
_connect(
|
connect(
|
||||||
context,
|
context,
|
||||||
peer.id,
|
peer.id,
|
||||||
isFileTransfer: isFileTransfer,
|
isFileTransfer: isFileTransfer,
|
||||||
@ -434,7 +409,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
proc: () {
|
proc: () {
|
||||||
_connect(context, id, isRDP: true);
|
connect(context, id, isRDP: true);
|
||||||
},
|
},
|
||||||
dismissOnClicked: true,
|
dismissOnClicked: true,
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ import '../../models/platform_model.dart';
|
|||||||
import '../../common/shared_state.dart';
|
import '../../common/shared_state.dart';
|
||||||
import './popup_menu.dart';
|
import './popup_menu.dart';
|
||||||
import './material_mod_popup_menu.dart' as mod_menu;
|
import './material_mod_popup_menu.dart' as mod_menu;
|
||||||
|
import './utils.dart';
|
||||||
|
|
||||||
class _MenubarTheme {
|
class _MenubarTheme {
|
||||||
static const Color commonColor = MyTheme.accent;
|
static const Color commonColor = MyTheme.accent;
|
||||||
@ -225,7 +226,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
),
|
),
|
||||||
tooltip: translate('Control Actions'),
|
tooltip: translate('Control Actions'),
|
||||||
position: mod_menu.PopupMenuPosition.under,
|
position: mod_menu.PopupMenuPosition.under,
|
||||||
itemBuilder: (BuildContext context) => _getControlMenu()
|
itemBuilder: (BuildContext context) => _getControlMenu(context)
|
||||||
.map((entry) => entry.build(
|
.map((entry) => entry.build(
|
||||||
context,
|
context,
|
||||||
const MenuConfig(
|
const MenuConfig(
|
||||||
@ -297,66 +298,76 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MenuEntryBase<String>> _getControlMenu() {
|
List<MenuEntryBase<String>> _getControlMenu(BuildContext context) {
|
||||||
final pi = widget.ffi.ffiModel.pi;
|
final pi = widget.ffi.ffiModel.pi;
|
||||||
final perms = widget.ffi.ffiModel.permissions;
|
final perms = widget.ffi.ffiModel.permissions;
|
||||||
|
|
||||||
final List<MenuEntryBase<String>> displayMenu = [];
|
final List<MenuEntryBase<String>> displayMenu = [];
|
||||||
|
displayMenu.addAll([
|
||||||
if (pi.version.isNotEmpty) {
|
MenuEntryButton<String>(
|
||||||
displayMenu.add(MenuEntryButton<String>(
|
childBuilder: (TextStyle? style) => Row(
|
||||||
childBuilder: (TextStyle? style) => Text(
|
children: [
|
||||||
translate('Refresh'),
|
Text(
|
||||||
style: style,
|
translate('OS Password'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
onPressed: () => showSetOSPassword(
|
||||||
|
widget.id, false, widget.ffi.dialogManager),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
],
|
||||||
),
|
),
|
||||||
proc: () {
|
proc: () {
|
||||||
bind.sessionRefresh(id: widget.id);
|
showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
|
||||||
},
|
},
|
||||||
dismissOnClicked: true,
|
dismissOnClicked: true,
|
||||||
));
|
|
||||||
}
|
|
||||||
displayMenu.add(MenuEntryButton<String>(
|
|
||||||
childBuilder: (TextStyle? style) => Text(
|
|
||||||
translate('OS Password'),
|
|
||||||
style: style,
|
|
||||||
),
|
),
|
||||||
proc: () {
|
MenuEntryButton<String>(
|
||||||
showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
|
|
||||||
},
|
|
||||||
dismissOnClicked: true,
|
|
||||||
));
|
|
||||||
|
|
||||||
if (!isWebDesktop) {
|
|
||||||
if (perms['keyboard'] != false && perms['clipboard'] != false) {
|
|
||||||
displayMenu.add(MenuEntryButton<String>(
|
|
||||||
childBuilder: (TextStyle? style) => Text(
|
|
||||||
translate('Paste'),
|
|
||||||
style: style,
|
|
||||||
),
|
|
||||||
proc: () {
|
|
||||||
() async {
|
|
||||||
ClipboardData? data =
|
|
||||||
await Clipboard.getData(Clipboard.kTextPlain);
|
|
||||||
if (data != null && data.text != null) {
|
|
||||||
bind.sessionInputString(id: widget.id, value: data.text ?? "");
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
},
|
|
||||||
dismissOnClicked: true,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
displayMenu.add(MenuEntryButton<String>(
|
|
||||||
childBuilder: (TextStyle? style) => Text(
|
childBuilder: (TextStyle? style) => Text(
|
||||||
translate('Reset canvas'),
|
translate('Transfer File'),
|
||||||
style: style,
|
style: style,
|
||||||
),
|
),
|
||||||
proc: () {
|
proc: () {
|
||||||
widget.ffi.cursorModel.reset();
|
connect(context, widget.id, isFileTransfer: true);
|
||||||
},
|
},
|
||||||
dismissOnClicked: true,
|
dismissOnClicked: true,
|
||||||
));
|
),
|
||||||
}
|
MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('TCP Tunneling'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
connect(context, widget.id, isTcpTunneling: true);
|
||||||
|
},
|
||||||
|
dismissOnClicked: true,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// {handler.get_audit_server() && <li #note>{translate('Note')}</li>}
|
||||||
|
final auditServer = bind.sessionGetAuditServerSync(id: widget.id);
|
||||||
|
//if (auditServer.isNotEmpty) {
|
||||||
|
displayMenu.add(
|
||||||
|
MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Note'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
showAuditDialog(widget.id, widget.ffi.dialogManager);
|
||||||
|
},
|
||||||
|
dismissOnClicked: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
//}
|
||||||
|
|
||||||
|
displayMenu.add(MenuEntryDivider());
|
||||||
|
|
||||||
if (perms['keyboard'] != false) {
|
if (perms['keyboard'] != false) {
|
||||||
if (pi.platform == 'Linux' || pi.sasEnabled) {
|
if (pi.platform == 'Linux' || pi.sasEnabled) {
|
||||||
@ -371,7 +382,24 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
dismissOnClicked: true,
|
dismissOnClicked: true,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (gFFI.ffiModel.permissions["restart"] != false &&
|
||||||
|
(pi.platform == "Linux" ||
|
||||||
|
pi.platform == "Windows" ||
|
||||||
|
pi.platform == "Mac OS")) {
|
||||||
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Restart Remote Device'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
|
||||||
|
},
|
||||||
|
dismissOnClicked: true,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perms['keyboard'] != false) {
|
||||||
displayMenu.add(MenuEntryButton<String>(
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
childBuilder: (TextStyle? style) => Text(
|
childBuilder: (TextStyle? style) => Text(
|
||||||
translate('Insert Lock'),
|
translate('Insert Lock'),
|
||||||
@ -402,17 +430,46 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gFFI.ffiModel.permissions["restart"] != false &&
|
if (pi.version.isNotEmpty) {
|
||||||
(pi.platform == "Linux" ||
|
|
||||||
pi.platform == "Windows" ||
|
|
||||||
pi.platform == "Mac OS")) {
|
|
||||||
displayMenu.add(MenuEntryButton<String>(
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
childBuilder: (TextStyle? style) => Text(
|
childBuilder: (TextStyle? style) => Text(
|
||||||
translate('Restart Remote Device'),
|
translate('Refresh'),
|
||||||
style: style,
|
style: style,
|
||||||
),
|
),
|
||||||
proc: () {
|
proc: () {
|
||||||
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
|
bind.sessionRefresh(id: widget.id);
|
||||||
|
},
|
||||||
|
dismissOnClicked: true,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isWebDesktop) {
|
||||||
|
// if (perms['keyboard'] != false && perms['clipboard'] != false) {
|
||||||
|
// displayMenu.add(MenuEntryButton<String>(
|
||||||
|
// childBuilder: (TextStyle? style) => Text(
|
||||||
|
// translate('Paste'),
|
||||||
|
// style: style,
|
||||||
|
// ),
|
||||||
|
// proc: () {
|
||||||
|
// () async {
|
||||||
|
// ClipboardData? data =
|
||||||
|
// await Clipboard.getData(Clipboard.kTextPlain);
|
||||||
|
// if (data != null && data.text != null) {
|
||||||
|
// bind.sessionInputString(id: widget.id, value: data.text ?? "");
|
||||||
|
// }
|
||||||
|
// }();
|
||||||
|
// },
|
||||||
|
// dismissOnClicked: true,
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Reset canvas'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
widget.ffi.cursorModel.reset();
|
||||||
},
|
},
|
||||||
dismissOnClicked: true,
|
dismissOnClicked: true,
|
||||||
));
|
));
|
||||||
@ -684,3 +741,76 @@ void showSetOSPassword(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showAuditDialog(String id, dialogManager) async {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
dialogManager.show((setState, close) {
|
||||||
|
submit() {
|
||||||
|
var text = controller.text.trim();
|
||||||
|
if (text != "") {
|
||||||
|
bind.sessionSendNote(id: id, note: text);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
late final focusNode = FocusNode(
|
||||||
|
onKey: (FocusNode node, RawKeyEvent evt) {
|
||||||
|
if (evt.logicalKey.keyLabel == 'Enter') {
|
||||||
|
if (evt is RawKeyDownEvent) {
|
||||||
|
int pos = controller.selection.base.offset;
|
||||||
|
controller.text =
|
||||||
|
'${controller.text.substring(0, pos)}\n${controller.text.substring(pos)}';
|
||||||
|
controller.selection =
|
||||||
|
TextSelection.fromPosition(TextPosition(offset: pos + 1));
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
if (evt.logicalKey.keyLabel == 'Esc') {
|
||||||
|
if (evt is RawKeyDownEvent) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return CustomAlertDialog(
|
||||||
|
title: Text(translate('Note')),
|
||||||
|
content: SizedBox(
|
||||||
|
width: 250,
|
||||||
|
height: 120,
|
||||||
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
textInputAction: TextInputAction.newline,
|
||||||
|
decoration: const InputDecoration.collapsed(
|
||||||
|
hintText: "input note here",
|
||||||
|
),
|
||||||
|
// inputFormatters: [
|
||||||
|
// LengthLimitingTextInputFormatter(16),
|
||||||
|
// // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
|
||||||
|
// ],
|
||||||
|
maxLines: null,
|
||||||
|
maxLength: 256,
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
)),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
style: flatButtonStyle,
|
||||||
|
onPressed: close,
|
||||||
|
child: Text(translate('Cancel')),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: flatButtonStyle,
|
||||||
|
onPressed: submit,
|
||||||
|
child: Text(translate('OK')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSubmit: submit,
|
||||||
|
onCancel: close,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
28
flutter/lib/desktop/widgets/utils.dart
Normal file
28
flutter/lib/desktop/widgets/utils.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||||
|
|
||||||
|
/// Connect to a peer with [id].
|
||||||
|
/// If [isFileTransfer], starts a session only for file transfer.
|
||||||
|
/// If [isTcpTunneling], starts a session only for tcp tunneling.
|
||||||
|
/// If [isRDP], starts a session only for rdp.
|
||||||
|
void connect(BuildContext context, String id,
|
||||||
|
{bool isFileTransfer = false,
|
||||||
|
bool isTcpTunneling = false,
|
||||||
|
bool isRDP = false}) async {
|
||||||
|
if (id == '') return;
|
||||||
|
id = id.replaceAll(' ', '');
|
||||||
|
assert(!(isFileTransfer && isTcpTunneling && isRDP),
|
||||||
|
"more than one connect type");
|
||||||
|
|
||||||
|
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||||
|
if (isFileTransfer) {
|
||||||
|
await rustDeskWinManager.newFileTransfer(id);
|
||||||
|
} else if (isTcpTunneling || isRDP) {
|
||||||
|
await rustDeskWinManager.newPortForward(id, isRDP);
|
||||||
|
} else {
|
||||||
|
await rustDeskWinManager.newRemoteDesktop(id);
|
||||||
|
}
|
||||||
|
if (!currentFocus.hasPrimaryFocus) {
|
||||||
|
currentFocus.unfocus();
|
||||||
|
}
|
||||||
|
}
|
@ -17,15 +17,14 @@ use crate::flutter::{self, SESSIONS};
|
|||||||
use crate::start_server;
|
use crate::start_server;
|
||||||
use crate::ui_interface;
|
use crate::ui_interface;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
use crate::ui_interface::change_id;
|
|
||||||
use crate::ui_interface::{
|
use crate::ui_interface::{
|
||||||
check_mouse_time, check_super_user_permission, discover, forget_password, get_api_server,
|
change_id, check_mouse_time, check_super_user_permission, discover, forget_password,
|
||||||
get_app_name, get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers,
|
get_api_server, get_app_name, get_async_job_status, get_connect_status, get_fav, get_id,
|
||||||
get_langs, get_license, get_local_option, get_mouse_time, get_option, get_options, get_peer,
|
get_lan_peers, get_langs, get_license, get_local_option, get_mouse_time, get_option,
|
||||||
get_peer_option, get_socks, get_sound_inputs, get_uuid, get_version, has_hwcodec,
|
get_options, get_peer, get_peer_option, get_socks, get_sound_inputs, get_uuid, get_version,
|
||||||
has_rendezvous_service, post_request, send_to_cm, set_local_option, set_option, set_options,
|
has_hwcodec, has_rendezvous_service, post_request, send_to_cm, set_local_option, set_option,
|
||||||
set_peer_option, set_permanent_password, set_socks, store_fav, test_if_valid_server,
|
set_options, set_peer_option, set_permanent_password, set_socks, store_fav,
|
||||||
update_temporary_password, using_public_server,
|
test_if_valid_server, update_temporary_password, using_public_server,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
client::file_trait::FileManager,
|
client::file_trait::FileManager,
|
||||||
@ -810,6 +809,21 @@ pub fn session_restart_remote_device(id: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_get_audit_server_sync(id: String) -> SyncReturn<String> {
|
||||||
|
let res = if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||||
|
session.get_audit_server()
|
||||||
|
} else {
|
||||||
|
"".to_owned()
|
||||||
|
};
|
||||||
|
SyncReturn(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_send_note(id: String, note: String) {
|
||||||
|
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||||
|
session.send_note(note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main_set_home_dir(home: String) {
|
pub fn main_set_home_dir(home: String) {
|
||||||
*config::APP_HOME_DIR.write().unwrap() = home;
|
*config::APP_HOME_DIR.write().unwrap() = home;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user