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);