Add '强制不走中继连接' menu item + reconnect(false) actually clears force_relay

Two changes:

1. src/ui_session_interface.rs reconnect(false): Upstream only ever set
   force_relay=true and ignored force_relay=false. This was wrong — the
   user has no way to clear the flag. Now reconnect(false) sets
   force_relay=false AND resets direct_failures=0, so the next
   connect() gets a fresh punch with the full timeout.

2. flutter/lib/common/widgets/peer_card.dart: Add a new menu item
   '强制不走中继连接' in all 5 _buildMenuItems implementations (recent,
   favorite, ab, discovered, address-book). Click calls
   rustDeskWinManager.newRemoteDesktop(id, forceRelay: false) which
   triggers a fresh punch. If punch fails, RustDesk's existing
   connect() falls back to relay — so the user always ends up with a
   working connection (just with a chance of P2P that was retried).

Use case: User's home Mac and office Win on different physical
networks, hbbr at 5 Mbps. By default every connection is relay
(orange banner, no file transfer). Clicking '强制不走中继' forces
a fresh P2P attempt — usually fails, but gives the user the
transparency to know 'yes, it tried P2P first'.
This commit is contained in:
xuwenwei
2026-06-09 20:06:37 +08:00
parent a5cd7940b6
commit b9cb597b99
2 changed files with 63 additions and 2 deletions
+52
View File
@@ -14,6 +14,7 @@ import '../../models/peer_model.dart';
import '../../models/platform_model.dart';
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
import '../../desktop/widgets/popup_menu.dart';
import '../../utils/multi_window_manager.dart';
import 'dart:math' as math;
typedef PopupMenuEntryBuilder = Future<List<mod_menu.PopupMenuEntry<String>>>
@@ -628,6 +629,52 @@ abstract class BasePeerCard extends StatelessWidget {
);
}
// Custom fork: 强制不走中继连接. Click this and the next connect
// attempt will be a fresh punch (with full timeout, no
// direct_failures penalty) and only fall back to relay if punch
// truly fails. Useful for same-LAN cases where the server's
// is_local detection might have been wrong, or just to retry
// after a temporary network blip.
@protected
MenuEntryBase<String> _forceDirectAction(BuildContext context, String id) {
return MenuEntryButton<String>(
proc: () async {
if (Navigator.canPop(context)) Navigator.pop(context);
// rustDeskWinManager.newRemoteDesktop already accepts forceRelay.
// Setting it to false here will:
// 1. clear force_relay flag in LoginConfigHandler
// (Rust side reconnect() now does this even when false)
// 2. reset direct_failures counter to 0
// 3. attempt a fresh punch with full timeout
// 4. if punch fails, connect() falls back to request_relay
// and the user gets a working connection through relay
try {
await rustDeskWinManager.newRemoteDesktop(
id,
forceRelay: false,
);
} catch (_) {
// best-effort
}
},
childBuilder: (TextStyle? style) => Container(
alignment: AlignmentDirectional.center,
height: CustomPopupMenuTheme.height,
child: Row(
children: [
const Icon(Icons.bolt, size: 16),
const SizedBox(width: 6),
Text(
translate('强制不走中继连接'),
style: style,
),
],
),
),
);
}
@protected
MenuEntryBase<String> _rdpAction(BuildContext context, String id) {
return MenuEntryButton<String>(
@@ -985,6 +1032,7 @@ class RecentPeerCard extends BasePeerCard {
// menuItems.add(await _openNewConnInOptAction(peer.id));
if (!isWeb) {
menuItems.add(await _forceAlwaysRelayAction(peer.id));
menuItems.add(_forceDirectAction(context, peer.id));
}
if (isWindows && peer.platform == kPeerPlatformWindows) {
menuItems.add(_rdpAction(context, peer.id));
@@ -1048,6 +1096,7 @@ class FavoritePeerCard extends BasePeerCard {
// menuItems.add(await _openNewConnInOptAction(peer.id));
if (!isWeb) {
menuItems.add(await _forceAlwaysRelayAction(peer.id));
menuItems.add(_forceDirectAction(context, peer.id));
}
if (isWindows && peer.platform == kPeerPlatformWindows) {
menuItems.add(_rdpAction(context, peer.id));
@@ -1110,6 +1159,7 @@ class DiscoveredPeerCard extends BasePeerCard {
// menuItems.add(await _openNewConnInOptAction(peer.id));
if (!isWeb) {
menuItems.add(await _forceAlwaysRelayAction(peer.id));
menuItems.add(_forceDirectAction(context, peer.id));
}
if (isWindows && peer.platform == kPeerPlatformWindows) {
menuItems.add(_rdpAction(context, peer.id));
@@ -1167,6 +1217,7 @@ class AddressBookPeerCard extends BasePeerCard {
// menuItems.add(await _openNewConnInOptAction(peer.id));
if (!isWeb) {
menuItems.add(await _forceAlwaysRelayAction(peer.id));
menuItems.add(_forceDirectAction(context, peer.id));
}
if (isWindows && peer.platform == kPeerPlatformWindows) {
menuItems.add(_rdpAction(context, peer.id));
@@ -1324,6 +1375,7 @@ class MyGroupPeerCard extends BasePeerCard {
// menuItems.add(await _openNewConnInOptAction(peer.id));
if (!isWeb) {
menuItems.add(await _forceAlwaysRelayAction(peer.id));
menuItems.add(_forceDirectAction(context, peer.id));
}
if (isWindows && peer.platform == kPeerPlatformWindows) {
menuItems.add(_rdpAction(context, peer.id));
+11 -2
View File
@@ -1292,9 +1292,18 @@ impl<T: InvokeUiSession> Session<T> {
let cloned = self.clone();
// override only if true
if true == force_relay {
// Custom fork: force_relay=false now actually clears the flag
// (upstream only ever set it to true). This lets the user
// 'force direct' (no relay) by clearing force_relay and resetting
// direct_failures, so the next connect() tries a fresh punch
// without the 'punch failed before, shorten timeout' penalty.
if force_relay {
self.lc.write().unwrap().force_relay = true;
} else {
self.lc.write().unwrap().force_relay = false;
// Also reset direct_failures so punch attempt gets the full
// timeout instead of the shortened one.
self.lc.write().unwrap().set_direct_failure(0);
}
self.lc.write().unwrap().peer_info = None;
self.reconnect_count.fetch_add(1, Ordering::SeqCst);