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

View File

@ -1,4 +1,5 @@
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/peers_view.dart';
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
@ -237,29 +238,32 @@ class _AddressBookState extends State<AddressBook> {
}
void abAddId() async {
var field = "";
var msg = "";
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) {
submit() async {
setState(() {
msg = "";
isInProgress = true;
errorMsg = null;
});
field = controller.text.trim();
if (field.isEmpty) {
String id = idController.id;
if (id.isEmpty) {
// pass
} else {
final ids = field.trim().split(RegExp(r"[\s,;\n]+"));
field = ids.join(',');
for (final newId in ids) {
if (gFFI.abModel.idContainBy(newId)) {
continue;
}
gFFI.abModel.addId(newId);
if (gFFI.abModel.idContainBy(id)) {
setState(() {
isInProgress = false;
errorMsg = translate('ID already exists');
});
return;
}
gFFI.abModel.addId(id, aliasController.text.trim(), selectedTag);
await gFFI.abModel.pushAb();
this.setState(() {});
// final currentPeers
@ -272,21 +276,70 @@ class _AddressBookState extends State<AddressBook> {
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("whitelist_sep")),
const SizedBox(
height: 8.0,
),
Row(
Column(
children: [
Expanded(
child: TextField(
maxLines: null,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
Align(
alignment: Alignment.centerLeft,
child: Row(
children: [
Text(
'*',
style: TextStyle(color: Colors.red, fontSize: 14),
),
controller: controller,
focusNode: FocusNode()..requestFocus()),
Text(
'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() {
final peer = super.widget.peer;
final name =
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
return Card(
margin: EdgeInsets.symmetric(horizontal: 2),
child: GestureDetector(
@ -90,7 +93,7 @@ class _PeerCardState extends State<_PeerCard>
? formatID(peer.id)
: peer.alias)
]),
Text('${peer.username}@${peer.hostname}')
Text(name)
],
).paddingOnly(left: 8.0),
),
@ -145,6 +148,8 @@ class _PeerCardState extends State<_PeerCard>
Widget _buildPeerTile(
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
final name =
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
final greyStyle = TextStyle(
fontSize: 11,
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
@ -184,7 +189,7 @@ class _PeerCardState extends State<_PeerCard>
Align(
alignment: Alignment.centerLeft,
child: Text(
'${peer.username}@${peer.hostname}',
name,
style: greyStyle,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
@ -206,7 +211,8 @@ class _PeerCardState extends State<_PeerCard>
Widget _buildPeerCard(
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(
color: Colors.transparent,
elevation: 0,
@ -310,11 +316,20 @@ class _PeerCardState extends State<_PeerCard>
bool get wantKeepAlive => true;
}
enum CardType {
recent,
fav,
lan,
ab,
}
abstract class BasePeerCard extends StatelessWidget {
final Peer peer;
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);
@override
@ -419,7 +434,7 @@ abstract class BasePeerCard extends StatelessWidget {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
_rdpDialog(id);
_rdpDialog(id, cardType);
},
)),
))
@ -471,17 +486,16 @@ abstract class BasePeerCard extends StatelessWidget {
switchType: SwitchType.scheckbox,
text: translate('Always connect via relay'),
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 {
String value;
String oldValue = await bind.mainGetPeerOption(id: id, key: option);
if (oldValue.isEmpty) {
value = 'Y';
} else {
value = '';
}
await bind.mainSetPeerOption(id: id, key: option, value: value);
gFFI.abModel.setPeerForceAlwaysRelay(id, v);
await bind.mainSetPeerOption(
id: id, key: option, value: bool2option('force-always-relay', v));
},
padding: menuPadding,
dismissOnClicked: true,
@ -489,14 +503,14 @@ abstract class BasePeerCard extends StatelessWidget {
}
@protected
MenuEntryBase<String> _renameAction(String id, bool isAddressBook) {
MenuEntryBase<String> _renameAction(String id) {
return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Rename'),
style: style,
),
proc: () {
_rename(id, isAddressBook);
_rename(id);
},
padding: menuPadding,
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;
var name = peer.alias;
var controller = TextEditingController(text: name);
if (isAddressBook) {
final peer = gFFI.abModel.peers.firstWhereOrNull((p) => id == p.id);
if (peer == null) {
// this should not happen
} else {
name = peer.alias;
}
String name;
if (cardType == CardType.ab) {
name = gFFI.abModel.find(id)?.alias ?? "";
} else {
name = await bind.mainGetPeerOption(id: id, key: 'alias');
}
var controller = TextEditingController(text: name);
gFFI.dialogManager.show((setState, close) {
submit() async {
isInProgress.value = true;
name = controller.text;
String name = controller.text.trim();
await bind.mainSetPeerAlias(id: id, alias: name);
if (isAddressBook) {
gFFI.abModel.setPeerAlias(id, name);
await gFFI.abModel.pushAb();
}
if (isAddressBook) {
gFFI.abModel.pullAb();
} else {
bind.mainLoadRecentPeers();
bind.mainLoadFavPeers();
}
gFFI.abModel.setPeerAlias(id, name);
update();
close();
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 {
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
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -671,7 +715,7 @@ class RecentPeerCard extends BasePeerCard {
menuItems.add(_createShortCutAction(peer.id));
}
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false));
menuItems.add(_renameAction(peer.id));
menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadRecentPeers();
}));
@ -679,13 +723,20 @@ class RecentPeerCard extends BasePeerCard {
menuItems.add(_unrememberPasswordAction(peer.id));
}
menuItems.add(_addFavAction(peer.id));
if (!gFFI.abModel.idContainBy(peer.id)) {
menuItems.add(_addToAb(peer));
}
return menuItems;
}
}
class FavoritePeerCard extends BasePeerCard {
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
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -706,7 +757,7 @@ class FavoritePeerCard extends BasePeerCard {
menuItems.add(_createShortCutAction(peer.id));
}
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false));
menuItems.add(_renameAction(peer.id));
menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadFavPeers();
}));
@ -716,13 +767,20 @@ class FavoritePeerCard extends BasePeerCard {
menuItems.add(_rmFavAction(peer.id, () async {
await bind.mainLoadFavPeers();
}));
if (!gFFI.abModel.idContainBy(peer.id)) {
menuItems.add(_addToAb(peer));
}
return menuItems;
}
}
class DiscoveredPeerCard extends BasePeerCard {
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
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -744,13 +802,20 @@ class DiscoveredPeerCard extends BasePeerCard {
}
menuItems.add(MenuEntryDivider());
menuItems.add(_removeAction(peer.id, () async {}));
if (!gFFI.abModel.idContainBy(peer.id)) {
menuItems.add(_addToAb(peer));
}
return menuItems;
}
}
class AddressBookPeerCard extends BasePeerCard {
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
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -771,7 +836,7 @@ class AddressBookPeerCard extends BasePeerCard {
menuItems.add(_createShortCutAction(peer.id));
}
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false));
menuItems.add(_renameAction(peer.id));
menuItems.add(_removeAction(peer.id, () async {}));
if (await bind.mainPeerHasPassword(id: peer.id)) {
menuItems.add(_unrememberPasswordAction(peer.id));
@ -872,23 +937,33 @@ class AddressBookPeerCard extends BasePeerCard {
}
}
void _rdpDialog(String id) async {
final portController = TextEditingController(
text: await bind.mainGetPeerOption(id: id, key: 'rdp_port'));
final userController = TextEditingController(
text: await bind.mainGetPeerOption(id: id, key: 'rdp_username'));
void _rdpDialog(String id, CardType card) async {
String port, username;
if (card == CardType.ab) {
port = gFFI.abModel.find(id)?.rdpPort ?? '';
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(
text: await bind.mainGetPeerOption(id: id, key: 'rdp_password'));
RxBool secure = true.obs;
gFFI.dialogManager.show((setState, close) {
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(
id: id, key: 'rdp_port', value: portController.text.trim());
id: id, key: 'rdp_username', value: username);
await bind.mainSetPeerOption(
id: id, key: 'rdp_username', value: userController.text);
await bind.mainSetPeerOption(
id: id, key: 'rdp_password', value: passwordController.text);
id: id, key: 'rdp_password', value: password);
gFFI.abModel.setRdp(id, port, username);
close();
}

View File

@ -68,11 +68,21 @@ class AbModel {
peers.clear();
}
void addId(String id) async {
void addId(String id, String alias, List<dynamic> tags) {
if (idContainBy(id)) {
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 {
@ -112,6 +122,10 @@ class AbModel {
}
}
Peer? find(String id) {
return peers.firstWhereOrNull((e) => e.id == id);
}
bool idContainBy(String id) {
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);
if (it.isEmpty) {
debugPrint("$id is not exists");
return;
} else {
if (it.isNotEmpty) {
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;
String alias;
List<dynamic> tags;
bool forceAlwaysRelay = false;
String rdpPort;
String rdpUsername;
bool online = false;
Peer.fromJson(Map<String, dynamic> json)
@ -17,7 +20,10 @@ class Peer {
hostname = json['hostname'] ?? '',
platform = json['platform'] ?? '',
alias = json['alias'] ?? '',
tags = json['tags'] ?? [];
tags = json['tags'] ?? [],
forceAlwaysRelay = json['forceAlwaysRelay'] == 'true',
rdpPort = json['rdpPort'] ?? '',
rdpUsername = json['rdpUsername'] ?? '';
Map<String, dynamic> toJson() {
return <String, dynamic>{
@ -27,6 +33,9 @@ class Peer {
"platform": platform,
"alias": alias,
"tags": tags,
"forceAlwaysRelay": forceAlwaysRelay.toString(),
"rdpPort": rdpPort,
"rdpUsername": rdpUsername,
};
}
@ -37,16 +46,23 @@ class Peer {
required this.platform,
required this.alias,
required this.tags,
required this.forceAlwaysRelay,
required this.rdpPort,
required this.rdpUsername,
});
Peer.loading()
: this(
id: '...',
username: '...',
hostname: '...',
platform: '...',
alias: '',
tags: []);
id: '...',
username: '...',
hostname: '...',
platform: '...',
alias: '',
tags: [],
forceAlwaysRelay: false,
rdpPort: '',
rdpUsername: '',
);
}
class Peers extends ChangeNotifier {

View File

@ -214,7 +214,11 @@ pub fn core_main() -> Option<Vec<String>> {
return None;
} else if args[0] == "--password" {
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;
} 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)
}
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() {
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(),
),
])
})
.map(|(id, _, p)| peer_to_map(id, p))
.collect();
if let Some(s) = flutter::GLOBAL_EVENT_STREAM
.read()
@ -705,16 +671,7 @@ pub fn main_load_fav_peers() {
.into_iter()
.filter_map(|(id, _, p)| {
if favs.contains(&id) {
Some(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(),
),
]))
Some(peer_to_map(id, p))
} else {
None
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("hide_cm_tip", ""),
("wayland_experiment_tip", ""),
("Right click to select tabs", ""),
("Add to Address Book", ""),
].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]
pub fn get_lan_peers() -> Vec<HashMap<&'static str, String>> {
config::LanPeers::load()