Merge pull request #2464 from 21pages/ab

opt address book
This commit is contained in:
RustDesk 2022-12-06 12:40:59 +08:00 committed by GitHub
commit 723a3dfb7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 318 additions and 141 deletions

View File

@ -923,7 +923,8 @@ bool option2bool(String option, String value) {
} else if (option.startsWith("allow-") || } else if (option.startsWith("allow-") ||
option == "stop-service" || option == "stop-service" ||
option == "direct-server" || option == "direct-server" ||
option == "stop-rendezvous-service") { option == "stop-rendezvous-service" ||
option == "force-always-relay") {
res = value == "Y"; res = value == "Y";
} else { } else {
assert(false); assert(false);
@ -939,7 +940,8 @@ String bool2option(String option, bool b) {
} else if (option.startsWith('allow-') || } else if (option.startsWith('allow-') ||
option == "stop-service" || option == "stop-service" ||
option == "direct-server" || option == "direct-server" ||
option == "stop-rendezvous-service") { option == "stop-rendezvous-service" ||
option == "force-always-relay") {
res = b ? 'Y' : ''; res = b ? 'Y' : '';
} else { } else {
assert(false); assert(false);

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
@ -237,29 +238,32 @@ class _AddressBookState extends State<AddressBook> {
} }
void abAddId() async { void abAddId() async {
var field = "";
var msg = "";
var isInProgress = false; var isInProgress = false;
TextEditingController controller = TextEditingController(text: field); IDTextEditingController idController = IDTextEditingController(text: '');
TextEditingController aliasController = TextEditingController(text: '');
final tags = List.of(gFFI.abModel.tags);
var selectedTag = List<dynamic>.empty(growable: true).obs;
final style = TextStyle(fontSize: 14.0);
String? errorMsg;
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async { submit() async {
setState(() { setState(() {
msg = "";
isInProgress = true; isInProgress = true;
errorMsg = null;
}); });
field = controller.text.trim(); String id = idController.id;
if (field.isEmpty) { if (id.isEmpty) {
// pass // pass
} else { } else {
final ids = field.trim().split(RegExp(r"[\s,;\n]+")); if (gFFI.abModel.idContainBy(id)) {
field = ids.join(','); setState(() {
for (final newId in ids) { isInProgress = false;
if (gFFI.abModel.idContainBy(newId)) { errorMsg = translate('ID already exists');
continue; });
} return;
gFFI.abModel.addId(newId);
} }
gFFI.abModel.addId(id, aliasController.text.trim(), selectedTag);
await gFFI.abModel.pushAb(); await gFFI.abModel.pushAb();
this.setState(() {}); this.setState(() {});
// final currentPeers // final currentPeers
@ -272,21 +276,70 @@ class _AddressBookState extends State<AddressBook> {
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(translate("whitelist_sep")), Column(
const SizedBox(
height: 8.0,
),
Row(
children: [ children: [
Expanded( Align(
child: TextField( alignment: Alignment.centerLeft,
maxLines: null, child: Row(
decoration: InputDecoration( children: [
border: const OutlineInputBorder(), Text(
errorText: msg.isEmpty ? null : translate(msg), '*',
style: TextStyle(color: Colors.red, fontSize: 14),
), ),
controller: controller, Text(
focusNode: FocusNode()..requestFocus()), 'ID',
style: style,
),
],
),
),
TextField(
controller: idController,
inputFormatters: [IDTextInputFormatter()],
decoration: InputDecoration(
isDense: true,
border: OutlineInputBorder(),
errorText: errorMsg),
style: style,
),
Align(
alignment: Alignment.centerLeft,
child: Text(
translate('Alias'),
style: style,
),
).marginOnly(top: 8, bottom: 2),
TextField(
controller: aliasController,
decoration: InputDecoration(
border: OutlineInputBorder(),
isDense: true,
),
style: style,
),
Align(
alignment: Alignment.centerLeft,
child: Text(
translate('Tags'),
style: style,
),
).marginOnly(top: 8),
Container(
child: Wrap(
children: tags
.map((e) => AddressBookTag(
name: e,
tags: selectedTag,
onTap: () {
if (selectedTag.contains(e)) {
selectedTag.remove(e);
} else {
selectedTag.add(e);
}
},
showActionMenu: false))
.toList(growable: false),
),
), ),
], ],
), ),

View File

@ -56,6 +56,9 @@ class _PeerCardState extends State<_PeerCard>
Widget _buildMobile() { Widget _buildMobile() {
final peer = super.widget.peer; final peer = super.widget.peer;
final name =
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
return Card( return Card(
margin: EdgeInsets.symmetric(horizontal: 2), margin: EdgeInsets.symmetric(horizontal: 2),
child: GestureDetector( child: GestureDetector(
@ -90,7 +93,7 @@ class _PeerCardState extends State<_PeerCard>
? formatID(peer.id) ? formatID(peer.id)
: peer.alias) : peer.alias)
]), ]),
Text('${peer.username}@${peer.hostname}') Text(name)
], ],
).paddingOnly(left: 8.0), ).paddingOnly(left: 8.0),
), ),
@ -145,6 +148,8 @@ class _PeerCardState extends State<_PeerCard>
Widget _buildPeerTile( Widget _buildPeerTile(
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) { BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
final name =
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
final greyStyle = TextStyle( final greyStyle = TextStyle(
fontSize: 11, fontSize: 11,
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
@ -184,7 +189,7 @@ class _PeerCardState extends State<_PeerCard>
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
'${peer.username}@${peer.hostname}', name,
style: greyStyle, style: greyStyle,
textAlign: TextAlign.start, textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -206,7 +211,8 @@ class _PeerCardState extends State<_PeerCard>
Widget _buildPeerCard( Widget _buildPeerCard(
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) { BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
final name = '${peer.username}@${peer.hostname}'; final name =
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
return Card( return Card(
color: Colors.transparent, color: Colors.transparent,
elevation: 0, elevation: 0,
@ -310,11 +316,20 @@ class _PeerCardState extends State<_PeerCard>
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
} }
enum CardType {
recent,
fav,
lan,
ab,
}
abstract class BasePeerCard extends StatelessWidget { abstract class BasePeerCard extends StatelessWidget {
final Peer peer; final Peer peer;
final EdgeInsets? menuPadding; final EdgeInsets? menuPadding;
final CardType cardType;
BasePeerCard({required this.peer, this.menuPadding, Key? key}) BasePeerCard(
{required this.peer, required this.cardType, this.menuPadding, Key? key})
: super(key: key); : super(key: key);
@override @override
@ -419,7 +434,7 @@ abstract class BasePeerCard extends StatelessWidget {
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {
Navigator.pop(context); Navigator.pop(context);
} }
_rdpDialog(id); _rdpDialog(id, cardType);
}, },
)), )),
)) ))
@ -471,17 +486,16 @@ abstract class BasePeerCard extends StatelessWidget {
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
text: translate('Always connect via relay'), text: translate('Always connect via relay'),
getter: () async { getter: () async {
return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty; if (cardType == CardType.ab) {
return gFFI.abModel.find(id)?.forceAlwaysRelay ?? false;
} else {
return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty;
}
}, },
setter: (bool v) async { setter: (bool v) async {
String value; gFFI.abModel.setPeerForceAlwaysRelay(id, v);
String oldValue = await bind.mainGetPeerOption(id: id, key: option); await bind.mainSetPeerOption(
if (oldValue.isEmpty) { id: id, key: option, value: bool2option('force-always-relay', v));
value = 'Y';
} else {
value = '';
}
await bind.mainSetPeerOption(id: id, key: option, value: value);
}, },
padding: menuPadding, padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
@ -489,14 +503,14 @@ abstract class BasePeerCard extends StatelessWidget {
} }
@protected @protected
MenuEntryBase<String> _renameAction(String id, bool isAddressBook) { MenuEntryBase<String> _renameAction(String id) {
return MenuEntryButton<String>( return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text( childBuilder: (TextStyle? style) => Text(
translate('Rename'), translate('Rename'),
style: style, style: style,
), ),
proc: () { proc: () {
_rename(id, isAddressBook); _rename(id);
}, },
padding: menuPadding, padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
@ -586,33 +600,42 @@ abstract class BasePeerCard extends StatelessWidget {
); );
} }
void _rename(String id, bool isAddressBook) async { @protected
MenuEntryBase<String> _addToAb(Peer peer) {
return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Add to Address Book'),
style: style,
),
proc: () {
() async {
if (!gFFI.abModel.idContainBy(peer.id)) {
gFFI.abModel.addPeer(peer);
await gFFI.abModel.pushAb();
}
}();
},
padding: menuPadding,
dismissOnClicked: true,
);
}
void _rename(String id) async {
RxBool isInProgress = false.obs; RxBool isInProgress = false.obs;
var name = peer.alias; String name;
var controller = TextEditingController(text: name); if (cardType == CardType.ab) {
if (isAddressBook) { name = gFFI.abModel.find(id)?.alias ?? "";
final peer = gFFI.abModel.peers.firstWhereOrNull((p) => id == p.id); } else {
if (peer == null) { name = await bind.mainGetPeerOption(id: id, key: 'alias');
// this should not happen
} else {
name = peer.alias;
}
} }
var controller = TextEditingController(text: name);
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async { submit() async {
isInProgress.value = true; isInProgress.value = true;
name = controller.text; String name = controller.text.trim();
await bind.mainSetPeerAlias(id: id, alias: name); await bind.mainSetPeerAlias(id: id, alias: name);
if (isAddressBook) { gFFI.abModel.setPeerAlias(id, name);
gFFI.abModel.setPeerAlias(id, name); update();
await gFFI.abModel.pushAb();
}
if (isAddressBook) {
gFFI.abModel.pullAb();
} else {
bind.mainLoadRecentPeers();
bind.mainLoadFavPeers();
}
close(); close();
isInProgress.value = false; isInProgress.value = false;
} }
@ -646,11 +669,32 @@ abstract class BasePeerCard extends StatelessWidget {
); );
}); });
} }
void update() {
switch (cardType) {
case CardType.recent:
bind.mainLoadRecentPeers();
break;
case CardType.fav:
bind.mainLoadFavPeers();
break;
case CardType.lan:
bind.mainLoadLanPeers();
break;
case CardType.ab:
gFFI.abModel.pullAb();
break;
}
}
} }
class RecentPeerCard extends BasePeerCard { class RecentPeerCard extends BasePeerCard {
RecentPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) RecentPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key); : super(
peer: peer,
cardType: CardType.recent,
menuPadding: menuPadding,
key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -671,7 +715,7 @@ class RecentPeerCard extends BasePeerCard {
menuItems.add(_createShortCutAction(peer.id)); menuItems.add(_createShortCutAction(peer.id));
} }
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false)); menuItems.add(_renameAction(peer.id));
menuItems.add(_removeAction(peer.id, () async { menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadRecentPeers(); await bind.mainLoadRecentPeers();
})); }));
@ -679,13 +723,20 @@ class RecentPeerCard extends BasePeerCard {
menuItems.add(_unrememberPasswordAction(peer.id)); menuItems.add(_unrememberPasswordAction(peer.id));
} }
menuItems.add(_addFavAction(peer.id)); menuItems.add(_addFavAction(peer.id));
if (!gFFI.abModel.idContainBy(peer.id)) {
menuItems.add(_addToAb(peer));
}
return menuItems; return menuItems;
} }
} }
class FavoritePeerCard extends BasePeerCard { class FavoritePeerCard extends BasePeerCard {
FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key); : super(
peer: peer,
cardType: CardType.fav,
menuPadding: menuPadding,
key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -706,7 +757,7 @@ class FavoritePeerCard extends BasePeerCard {
menuItems.add(_createShortCutAction(peer.id)); menuItems.add(_createShortCutAction(peer.id));
} }
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false)); menuItems.add(_renameAction(peer.id));
menuItems.add(_removeAction(peer.id, () async { menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadFavPeers(); await bind.mainLoadFavPeers();
})); }));
@ -716,13 +767,20 @@ class FavoritePeerCard extends BasePeerCard {
menuItems.add(_rmFavAction(peer.id, () async { menuItems.add(_rmFavAction(peer.id, () async {
await bind.mainLoadFavPeers(); await bind.mainLoadFavPeers();
})); }));
if (!gFFI.abModel.idContainBy(peer.id)) {
menuItems.add(_addToAb(peer));
}
return menuItems; return menuItems;
} }
} }
class DiscoveredPeerCard extends BasePeerCard { class DiscoveredPeerCard extends BasePeerCard {
DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key); : super(
peer: peer,
cardType: CardType.lan,
menuPadding: menuPadding,
key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -744,13 +802,20 @@ class DiscoveredPeerCard extends BasePeerCard {
} }
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
menuItems.add(_removeAction(peer.id, () async {})); menuItems.add(_removeAction(peer.id, () async {}));
if (!gFFI.abModel.idContainBy(peer.id)) {
menuItems.add(_addToAb(peer));
}
return menuItems; return menuItems;
} }
} }
class AddressBookPeerCard extends BasePeerCard { class AddressBookPeerCard extends BasePeerCard {
AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key); : super(
peer: peer,
cardType: CardType.ab,
menuPadding: menuPadding,
key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -771,7 +836,7 @@ class AddressBookPeerCard extends BasePeerCard {
menuItems.add(_createShortCutAction(peer.id)); menuItems.add(_createShortCutAction(peer.id));
} }
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false)); menuItems.add(_renameAction(peer.id));
menuItems.add(_removeAction(peer.id, () async {})); menuItems.add(_removeAction(peer.id, () async {}));
if (await bind.mainPeerHasPassword(id: peer.id)) { if (await bind.mainPeerHasPassword(id: peer.id)) {
menuItems.add(_unrememberPasswordAction(peer.id)); menuItems.add(_unrememberPasswordAction(peer.id));
@ -872,23 +937,33 @@ class AddressBookPeerCard extends BasePeerCard {
} }
} }
void _rdpDialog(String id) async { void _rdpDialog(String id, CardType card) async {
final portController = TextEditingController( String port, username;
text: await bind.mainGetPeerOption(id: id, key: 'rdp_port')); if (card == CardType.ab) {
final userController = TextEditingController( port = gFFI.abModel.find(id)?.rdpPort ?? '';
text: await bind.mainGetPeerOption(id: id, key: 'rdp_username')); username = gFFI.abModel.find(id)?.rdpUsername ?? '';
} else {
port = await bind.mainGetPeerOption(id: id, key: 'rdp_port');
username = await bind.mainGetPeerOption(id: id, key: 'rdp_username');
}
final portController = TextEditingController(text: port);
final userController = TextEditingController(text: username);
final passwordController = TextEditingController( final passwordController = TextEditingController(
text: await bind.mainGetPeerOption(id: id, key: 'rdp_password')); text: await bind.mainGetPeerOption(id: id, key: 'rdp_password'));
RxBool secure = true.obs; RxBool secure = true.obs;
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async { submit() async {
String port = portController.text.trim();
String username = userController.text;
String password = passwordController.text;
await bind.mainSetPeerOption(id: id, key: 'rdp_port', value: port);
await bind.mainSetPeerOption( await bind.mainSetPeerOption(
id: id, key: 'rdp_port', value: portController.text.trim()); id: id, key: 'rdp_username', value: username);
await bind.mainSetPeerOption( await bind.mainSetPeerOption(
id: id, key: 'rdp_username', value: userController.text); id: id, key: 'rdp_password', value: password);
await bind.mainSetPeerOption( gFFI.abModel.setRdp(id, port, username);
id: id, key: 'rdp_password', value: passwordController.text);
close(); close();
} }

View File

@ -68,11 +68,21 @@ class AbModel {
peers.clear(); peers.clear();
} }
void addId(String id) async { void addId(String id, String alias, List<dynamic> tags) {
if (idContainBy(id)) { if (idContainBy(id)) {
return; return;
} }
peers.add(Peer.fromJson({"id": id})); final peer = Peer.fromJson({
'id': id,
'alias': alias,
'tags': tags,
});
peers.add(peer);
}
void addPeer(Peer peer) {
peers.removeWhere((e) => e.id == peer.id);
peers.add(peer);
} }
void addTag(String tag) async { void addTag(String tag) async {
@ -112,6 +122,10 @@ class AbModel {
} }
} }
Peer? find(String id) {
return peers.firstWhereOrNull((e) => e.id == id);
}
bool idContainBy(String id) { bool idContainBy(String id) {
return peers.where((element) => element.id == id).isNotEmpty; return peers.where((element) => element.id == id).isNotEmpty;
} }
@ -150,13 +164,28 @@ class AbModel {
} }
} }
void setPeerAlias(String id, String value) { Future<void> setPeerAlias(String id, String value) async {
final it = peers.where((p0) => p0.id == id); final it = peers.where((p0) => p0.id == id);
if (it.isEmpty) { if (it.isNotEmpty) {
debugPrint("$id is not exists");
return;
} else {
it.first.alias = value; it.first.alias = value;
await pushAb();
}
}
Future<void> setPeerForceAlwaysRelay(String id, bool value) async {
final it = peers.where((p0) => p0.id == id);
if (it.isNotEmpty) {
it.first.forceAlwaysRelay = value;
await pushAb();
}
}
Future<void> setRdp(String id, String port, String username) async {
final it = peers.where((p0) => p0.id == id);
if (it.isNotEmpty) {
it.first.rdpPort = port;
it.first.rdpUsername = username;
await pushAb();
} }
} }

View File

@ -9,6 +9,9 @@ class Peer {
final String platform; final String platform;
String alias; String alias;
List<dynamic> tags; List<dynamic> tags;
bool forceAlwaysRelay = false;
String rdpPort;
String rdpUsername;
bool online = false; bool online = false;
Peer.fromJson(Map<String, dynamic> json) Peer.fromJson(Map<String, dynamic> json)
@ -17,7 +20,10 @@ class Peer {
hostname = json['hostname'] ?? '', hostname = json['hostname'] ?? '',
platform = json['platform'] ?? '', platform = json['platform'] ?? '',
alias = json['alias'] ?? '', alias = json['alias'] ?? '',
tags = json['tags'] ?? []; tags = json['tags'] ?? [],
forceAlwaysRelay = json['forceAlwaysRelay'] == 'true',
rdpPort = json['rdpPort'] ?? '',
rdpUsername = json['rdpUsername'] ?? '';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return <String, dynamic>{ return <String, dynamic>{
@ -27,6 +33,9 @@ class Peer {
"platform": platform, "platform": platform,
"alias": alias, "alias": alias,
"tags": tags, "tags": tags,
"forceAlwaysRelay": forceAlwaysRelay.toString(),
"rdpPort": rdpPort,
"rdpUsername": rdpUsername,
}; };
} }
@ -37,16 +46,23 @@ class Peer {
required this.platform, required this.platform,
required this.alias, required this.alias,
required this.tags, required this.tags,
required this.forceAlwaysRelay,
required this.rdpPort,
required this.rdpUsername,
}); });
Peer.loading() Peer.loading()
: this( : this(
id: '...', id: '...',
username: '...', username: '...',
hostname: '...', hostname: '...',
platform: '...', platform: '...',
alias: '', alias: '',
tags: []); tags: [],
forceAlwaysRelay: false,
rdpPort: '',
rdpUsername: '',
);
} }
class Peers extends ChangeNotifier { class Peers extends ChangeNotifier {

View File

@ -214,7 +214,11 @@ pub fn core_main() -> Option<Vec<String>> {
return None; return None;
} else if args[0] == "--password" { } else if args[0] == "--password" {
if args.len() == 2 { if args.len() == 2 {
crate::ipc::set_permanent_password(args[1].to_owned()).unwrap(); if crate::platform::is_root() {
crate::ipc::set_permanent_password(args[1].to_owned()).unwrap();
} else {
log::info!("Permission denied!");
}
} }
return None; return None;
} else if args[0] == "--check-hwcodec-config" { } else if args[0] == "--check-hwcodec-config" {

View File

@ -641,45 +641,11 @@ pub fn main_peer_has_password(id: String) -> bool {
peer_has_password(id) peer_has_password(id)
} }
pub fn main_get_recent_peers() -> String {
if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers()
.drain(..)
.map(|(id, _, p)| {
HashMap::<&str, String>::from_iter([
("id", id),
("username", p.info.username.clone()),
("hostname", p.info.hostname.clone()),
("platform", p.info.platform.clone()),
(
"alias",
p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(),
),
])
})
.collect();
serde_json::ser::to_string(&peers).unwrap_or("".to_owned())
} else {
String::new()
}
}
pub fn main_load_recent_peers() { pub fn main_load_recent_peers() {
if !config::APP_DIR.read().unwrap().is_empty() { if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers() let peers: Vec<HashMap<&str, String>> = PeerConfig::peers()
.drain(..) .drain(..)
.map(|(id, _, p)| { .map(|(id, _, p)| peer_to_map(id, p))
HashMap::<&str, String>::from_iter([
("id", id),
("username", p.info.username.clone()),
("hostname", p.info.hostname.clone()),
("platform", p.info.platform.clone()),
(
"alias",
p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(),
),
])
})
.collect(); .collect();
if let Some(s) = flutter::GLOBAL_EVENT_STREAM if let Some(s) = flutter::GLOBAL_EVENT_STREAM
.read() .read()
@ -705,16 +671,7 @@ pub fn main_load_fav_peers() {
.into_iter() .into_iter()
.filter_map(|(id, _, p)| { .filter_map(|(id, _, p)| {
if favs.contains(&id) { if favs.contains(&id) {
Some(HashMap::<&str, String>::from_iter([ Some(peer_to_map(id, p))
("id", id),
("username", p.info.username.clone()),
("hostname", p.info.hostname.clone()),
("platform", p.info.platform.clone()),
(
"alias",
p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(),
),
]))
} else { } else {
None None
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -400,5 +400,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", "右键选择选项卡"), ("Right click to select tabs", "右键选择选项卡"),
("Skipped", "已跳过"), ("Skipped", "已跳过"),
("Add to Address Book", "添加到地址簿"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "Dies ist nur möglich, wenn der Zugriff nur über ein permanentes Passwort erfolgt."), // Sehr unklar. Muss noch angepasst werden. Original: Allow hiding only if accepting sessions via password and using pernament passw"), ("hide_cm_tip", "Dies ist nur möglich, wenn der Zugriff nur über ein permanentes Passwort erfolgt."), // Sehr unklar. Muss noch angepasst werden. Original: Allow hiding only if accepting sessions via password and using pernament passw"),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "Permitir ocultar solo si se aceptan sesiones a través de contraseña y usando contraseña permanente"), ("hide_cm_tip", "Permitir ocultar solo si se aceptan sesiones a través de contraseña y usando contraseña permanente"),
("wayland_experiment_tip", "El soporte para Wayland está en fase experimental, por favor, use X11 si necesita acceso desatendido."), ("wayland_experiment_tip", "El soporte para Wayland está en fase experimental, por favor, use X11 si necesita acceso desatendido."),
("Right click to select tabs", "Clic derecho para seleccionar pestañas"), ("Right click to select tabs", "Clic derecho para seleccionar pestañas"),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "فقط در صورت پذیرفتن جلسات از طریق رمز عبور و استفاده از رمز عبور دائمی، مخفی شدن مجاز است"), ("hide_cm_tip", "فقط در صورت پذیرفتن جلسات از طریق رمز عبور و استفاده از رمز عبور دائمی، مخفی شدن مجاز است"),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "Autoriser le masquage uniquement si vous acceptez des sessions via un mot de passe et utilisez un mot de passe permanent"), ("hide_cm_tip", "Autoriser le masquage uniquement si vous acceptez des sessions via un mot de passe et utilisez un mot de passe permanent"),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "Να επιτρέπεται η απόκρυψη, μόνο εάν αποδέχεστε συνδέσεις μέσω κωδικού πρόσβασης και χρησιμοποιείτε μόνιμο κωδικό πρόσβασης"), ("hide_cm_tip", "Να επιτρέπεται η απόκρυψη, μόνο εάν αποδέχεστε συνδέσεις μέσω κωδικού πρόσβασης και χρησιμοποιείτε μόνιμο κωδικό πρόσβασης"),
("wayland_experiment_tip", "Η υποστήριξη Wayland βρίσκεται σε πειραματικό στάδιο, χρησιμοποιήστε το X11 εάν χρειάζεστε πρόσβαση χωρίς επίβλεψη."), ("wayland_experiment_tip", "Η υποστήριξη Wayland βρίσκεται σε πειραματικό στάδιο, χρησιμοποιήστε το X11 εάν χρειάζεστε πρόσβαση χωρίς επίβλεψη."),
("Right click to select tabs", "Κάντε δεξί κλικ για να επιλέξετε καρτέλες"), ("Right click to select tabs", "Κάντε δεξί κλικ για να επιλέξετε καρτέλες"),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "Permetti di nascondere solo se si accettano sessioni con password permanente"), ("hide_cm_tip", "Permetti di nascondere solo se si accettano sessioni con password permanente"),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "Разрешать скрытие случае, если принимаются сеансы по паролю или используется постоянный пароль"), ("hide_cm_tip", "Разрешать скрытие случае, если принимаются сеансы по паролю или используется постоянный пароль"),
("wayland_experiment_tip", "Поддержка Wayland находится на экспериментальной стадии, используйте X11, если вам требуется автоматический доступ."), ("wayland_experiment_tip", "Поддержка Wayland находится на экспериментальной стадии, используйте X11, если вам требуется автоматический доступ."),
("Right click to select tabs", "Выбор вкладок щелчком правой кнопки мыши"), ("Right click to select tabs", "Выбор вкладок щелчком правой кнопки мыши"),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "Kjo është e mundur vetëm nëse aksesi bëhet nëpërmjet një fjalëkalimi të përhershëm"), ("hide_cm_tip", "Kjo është e mundur vetëm nëse aksesi bëhet nëpërmjet një fjalëkalimi të përhershëm"),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "Tillåt att gömma endast om accepterande sessioner med lösenord och permanenta lösenord"), ("hide_cm_tip", "Tillåt att gömma endast om accepterande sessioner med lösenord och permanenta lösenord"),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", "在只允許密碼連接並且只用固定密碼的情況下才允許隱藏"), ("hide_cm_tip", "在只允許密碼連接並且只用固定密碼的情況下才允許隱藏"),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", "右鍵選擇選項卡"), ("Right click to select tabs", "右鍵選擇選項卡"),
("Add to Address Book", "添加到地址簿"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""), ("hide_cm_tip", ""),
("wayland_experiment_tip", ""), ("wayland_experiment_tip", ""),
("Right click to select tabs", ""), ("Right click to select tabs", ""),
("Add to Address Book", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -685,6 +685,19 @@ pub fn discover() {
}); });
} }
pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> {
HashMap::<&str, String>::from_iter([
("id", id),
("username", p.info.username.clone()),
("hostname", p.info.hostname.clone()),
("platform", p.info.platform.clone()),
(
"alias",
p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(),
),
])
}
#[inline] #[inline]
pub fn get_lan_peers() -> Vec<HashMap<&'static str, String>> { pub fn get_lan_peers() -> Vec<HashMap<&'static str, String>> {
config::LanPeers::load() config::LanPeers::load()