From b9cb597b996746673a8d4c8e98e9e9a9e2faf2b2 Mon Sep 17 00:00:00 2001 From: xuwenwei Date: Tue, 9 Jun 2026 20:06:37 +0800 Subject: [PATCH] =?UTF-8?q?Add=20'=E5=BC=BA=E5=88=B6=E4=B8=8D=E8=B5=B0?= =?UTF-8?q?=E4=B8=AD=E7=BB=A7=E8=BF=9E=E6=8E=A5'=20menu=20item=20+=20recon?= =?UTF-8?q?nect(false)=20actually=20clears=20force=5Frelay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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'. --- flutter/lib/common/widgets/peer_card.dart | 52 +++++++++++++++++++++++ src/ui_session_interface.rs | 13 +++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 1f9f3ed..709974b 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -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>> @@ -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 _forceDirectAction(BuildContext context, String id) { + return MenuEntryButton( + 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 _rdpAction(BuildContext context, String id) { return MenuEntryButton( @@ -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)); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index e6c8ac6..3fbc512 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -1292,9 +1292,18 @@ impl Session { 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);