diff --git a/electrum/gui/icons/bookmark.png b/electrum/gui/icons/bookmark.png
new file mode 100644
index 000000000..dd24a0c15
Binary files /dev/null and b/electrum/gui/icons/bookmark.png differ
diff --git a/electrum/gui/icons/bookmark.svg b/electrum/gui/icons/bookmark.svg
new file mode 100644
index 000000000..32123634d
--- /dev/null
+++ b/electrum/gui/icons/bookmark.svg
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/electrum/gui/icons/bookmark_add.png b/electrum/gui/icons/bookmark_add.png
new file mode 100644
index 000000000..430861c47
Binary files /dev/null and b/electrum/gui/icons/bookmark_add.png differ
diff --git a/electrum/gui/icons/bookmark_add.svg b/electrum/gui/icons/bookmark_add.svg
new file mode 100644
index 000000000..ed4b981d4
--- /dev/null
+++ b/electrum/gui/icons/bookmark_add.svg
@@ -0,0 +1,23 @@
+
+
+
\ No newline at end of file
diff --git a/electrum/gui/icons/bookmark_remove.png b/electrum/gui/icons/bookmark_remove.png
new file mode 100644
index 000000000..a75c86c6b
Binary files /dev/null and b/electrum/gui/icons/bookmark_remove.png differ
diff --git a/electrum/gui/icons/bookmark_remove.svg b/electrum/gui/icons/bookmark_remove.svg
new file mode 100644
index 000000000..a6036a19f
--- /dev/null
+++ b/electrum/gui/icons/bookmark_remove.svg
@@ -0,0 +1,23 @@
+
+
+
\ No newline at end of file
diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py
index 141987fc8..1ad70a090 100644
--- a/electrum/gui/qt/network_dialog.py
+++ b/electrum/gui/qt/network_dialog.py
@@ -123,6 +123,13 @@ class NodesListWidget(QTreeWidget):
def do_set_server():
self.setServer.emit(str(server))
menu.addAction(read_QIcon("chevron-right.png"), _("Use as server"), do_set_server)
+ def set_bookmark(*, add: bool):
+ self.network.set_server_bookmark(server, add=add)
+ self.update()
+ if self.network.is_server_bookmarked(server):
+ menu.addAction(read_QIcon("bookmark_remove.png"), _("Remove from bookmarks"), lambda: set_bookmark(add=False))
+ else:
+ menu.addAction(read_QIcon("bookmark_add.png"), _("Bookmark this server"), lambda: set_bookmark(add=True))
elif item_type == self.ItemType.CHAIN:
chain_id = item.data(0, self.CHAIN_ID_ROLE)
def do_follow_chain():
@@ -174,6 +181,8 @@ class NodesListWidget(QTreeWidget):
item.setToolTip(0, str(i.server))
if i == network.interface:
item.setIcon(0, read_QIcon("chevron-right.png"))
+ elif network.is_server_bookmarked(i.server):
+ item.setIcon(0, read_QIcon("bookmark.png"))
x.addChild(item)
if n_chains > 1:
connected_servers_item.addChild(x)
@@ -183,18 +192,21 @@ class NodesListWidget(QTreeWidget):
disconnected_servers_item.setData(0, self.ITEMTYPE_ROLE, self.ItemType.TOPLEVEL)
connected_hosts = set([iface.host for ifaces in chains.values() for iface in ifaces])
protocol = PREFERRED_NETWORK_PROTOCOL
- for _host, d in sorted(servers.items()):
- if _host in connected_hosts:
- continue
- if _host.endswith('.onion') and not use_tor:
+ server_addrs = [
+ ServerAddr(_host, port, protocol=protocol)
+ for _host, d in servers.items()
+ if (port := d.get(protocol))]
+ server_addrs.sort(key=lambda x: (-network.is_server_bookmarked(x), str(x)))
+ for server in server_addrs:
+ if server.host in connected_hosts:
continue
- port = d.get(protocol)
- if not port:
+ if server.host.endswith('.onion') and not use_tor:
continue
- server = ServerAddr(_host, port, protocol=protocol)
item = QTreeWidgetItem([server.net_addr_str(), ""])
item.setData(0, self.ITEMTYPE_ROLE, self.ItemType.DISCONNECTED_SERVER)
item.setData(0, self.SERVER_ADDR_ROLE, server)
+ if network.is_server_bookmarked(server):
+ item.setIcon(0, read_QIcon("bookmark.png"))
disconnected_servers_item.addChild(item)
self.addTopLevelItem(connected_servers_item)
diff --git a/electrum/network.py b/electrum/network.py
index d3c2b1b7e..ddef1aa8f 100644
--- a/electrum/network.py
+++ b/electrum/network.py
@@ -581,6 +581,18 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
out[server.host].update({server.protocol: port})
else:
out[server.host] = {server.protocol: port}
+ # add bookmarks
+ bookmarks = self.config.NETWORK_BOOKMARKED_SERVERS or []
+ for server_str in bookmarks:
+ try:
+ server = ServerAddr.from_str(server_str)
+ except ValueError:
+ continue
+ port = str(server.port)
+ if server.host in out:
+ out[server.host].update({server.protocol: port})
+ else:
+ out[server.host] = {server.protocol: port}
# potentially filter out some
if self.config.NETWORK_NOONION:
out = filter_noonion(out)
@@ -706,6 +718,22 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
self.oneserver = oneserver
self.num_server = NUM_TARGET_CONNECTED_SERVERS if not oneserver else 0
+ def is_server_bookmarked(self, server: ServerAddr) -> bool:
+ bookmarks = self.config.NETWORK_BOOKMARKED_SERVERS or []
+ return str(server) in bookmarks
+
+ def set_server_bookmark(self, server: ServerAddr, *, add: bool) -> None:
+ server_str = str(server)
+ with self.config.lock:
+ bookmarks = self.config.NETWORK_BOOKMARKED_SERVERS or []
+ if add:
+ if server_str not in bookmarks:
+ bookmarks.append(server_str)
+ else: # remove
+ if server_str in bookmarks:
+ bookmarks.remove(server_str)
+ self.config.NETWORK_BOOKMARKED_SERVERS = bookmarks
+
async def _switch_to_random_interface(self):
'''Switch to a random connected server other than the current one'''
servers = self.get_interfaces() # Those in connected state
diff --git a/electrum/simple_config.py b/electrum/simple_config.py
index 02d6a9db1..e3b410e6a 100644
--- a/electrum/simple_config.py
+++ b/electrum/simple_config.py
@@ -940,6 +940,7 @@ class SimpleConfig(Logger):
NETWORK_SERVERFINGERPRINT = ConfigVar('serverfingerprint', default=None, type_=str)
NETWORK_MAX_INCOMING_MSG_SIZE = ConfigVar('network_max_incoming_msg_size', default=1_000_000, type_=int) # in bytes
NETWORK_TIMEOUT = ConfigVar('network_timeout', default=None, type_=int)
+ NETWORK_BOOKMARKED_SERVERS = ConfigVar('network_bookmarked_servers', default=None)
WALLET_BATCH_RBF = ConfigVar(
'batch_rbf', default=False, type_=bool,