From 412cdb0dad16b551aea8d597e6f3b39ab50b8714 Mon Sep 17 00:00:00 2001 From: Eoin Motherway <25342760+YuKitsune@users.noreply.github.com> Date: Sun, 11 Oct 2026 14:18:57 +1000 Subject: [PATCH 1/5] wip: connection request --- .../OpenConnectionRequestWindowRequest.cs | 42 ++++++++++ .../RebuildCpdlcStatusLabelItemsRequest.cs | 16 +++- .../Messages/SendConnectionRequestRequest.cs | 26 +++++++ .../Server/SignalRConnectionManager.cs | 10 +++ .../ViewModels/ConnectionRequestViewModel.cs | 53 +++++++++++++ .../Windows/ConnectionRequestWindow.xaml | 78 +++++++++++++++++++ .../Windows/ConnectionRequestWindow.xaml.cs | 13 ++++ source/CPDLCPlugin/Windows/WindowKeys.cs | 1 + .../Mocks/TestClientManager.cs | 10 +++ source/CPDLCServer/Clients/ClientManager.cs | 11 +++ .../DownlinkReceivedNotificationHandler.cs | 7 ++ .../SendConnectionRequestCommandHandler.cs | 45 +++++++++++ source/CPDLCServer/Hubs/ControllerHub.cs | 18 +++++ .../Messages/SendConnectionRequestCommand.cs | 8 ++ source/CPDLCServer/Model/ControlMessages.cs | 5 ++ 15 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 source/CPDLCPlugin/Messages/OpenConnectionRequestWindowRequest.cs create mode 100644 source/CPDLCPlugin/Messages/SendConnectionRequestRequest.cs create mode 100644 source/CPDLCPlugin/ViewModels/ConnectionRequestViewModel.cs create mode 100644 source/CPDLCPlugin/Windows/ConnectionRequestWindow.xaml create mode 100644 source/CPDLCPlugin/Windows/ConnectionRequestWindow.xaml.cs create mode 100644 source/CPDLCServer/Handlers/SendConnectionRequestCommandHandler.cs create mode 100644 source/CPDLCServer/Messages/SendConnectionRequestCommand.cs diff --git a/source/CPDLCPlugin/Messages/OpenConnectionRequestWindowRequest.cs b/source/CPDLCPlugin/Messages/OpenConnectionRequestWindowRequest.cs new file mode 100644 index 0000000..27deaed --- /dev/null +++ b/source/CPDLCPlugin/Messages/OpenConnectionRequestWindowRequest.cs @@ -0,0 +1,42 @@ +using CPDLCPlugin.Configuration; +using CPDLCPlugin.ViewModels; +using CPDLCPlugin.Windows; +using MediatR; + +namespace CPDLCPlugin.Messages; + +public record OpenConnectionRequestWindowRequest(string Callsign) : IRequest; + +public class OpenConnectionRequestWindowRequestHandler( + WindowManager windowManager, + PluginConfiguration pluginConfiguration, + IMediator mediator, + Plugin plugin, + IErrorReporter errorReporter) + : IRequestHandler +{ + public Task Handle(OpenConnectionRequestWindowRequest request, CancellationToken cancellationToken) + { + // Each window is specific to a callsign, so we always want a fresh window + windowManager.TryRemoveWindow(WindowKeys.ConnectionRequest); + + windowManager.FocusOrCreateWindow( + WindowKeys.ConnectionRequest, + windowHandle => + { + var currentStationIdentifier = plugin.ConnectionManager?.StationIdentifier; + var viewModel = new ConnectionRequestViewModel( + mediator, + errorReporter, + windowHandle, + request.Callsign, + pluginConfiguration.Stations, + string.IsNullOrEmpty(currentStationIdentifier) ? pluginConfiguration.Stations.First() : currentStationIdentifier!); + + var control = new ConnectionRequestWindow(viewModel); + return control; + }); + + return Task.CompletedTask; + } +} diff --git a/source/CPDLCPlugin/Messages/RebuildCpdlcStatusLabelItemsRequest.cs b/source/CPDLCPlugin/Messages/RebuildCpdlcStatusLabelItemsRequest.cs index ea689f4..feb5399 100644 --- a/source/CPDLCPlugin/Messages/RebuildCpdlcStatusLabelItemsRequest.cs +++ b/source/CPDLCPlugin/Messages/RebuildCpdlcStatusLabelItemsRequest.cs @@ -84,7 +84,21 @@ public async Task Handle(RebuildCpdlcStatusLabelItemsRequest request, Cancellati if (isOnAcarsNetwork && connection is null) { text = "."; - // TODO: Left click will initiate a manual connection + onMouseClick = args => + { + try + { + if (args.Button != CustomLabelItemMouseButton.Left) + return; + + mediator.Send(new OpenConnectionRequestWindowRequest(callsign)); + args.Handled = true; + } + catch (Exception ex) + { + errorReporter.ReportError(ex, "Error opening CPDLC connection request window"); + } + }; } else if (connection is not null && connection.DataAuthorityState == DataAuthorityState.NextDataAuthority) { diff --git a/source/CPDLCPlugin/Messages/SendConnectionRequestRequest.cs b/source/CPDLCPlugin/Messages/SendConnectionRequestRequest.cs new file mode 100644 index 0000000..a482da2 --- /dev/null +++ b/source/CPDLCPlugin/Messages/SendConnectionRequestRequest.cs @@ -0,0 +1,26 @@ +using MediatR; +using Serilog; + +namespace CPDLCPlugin.Messages; + +public record SendConnectionRequestRequest(string Callsign, string StationId) : IRequest; + +public class SendConnectionRequestRequestHandler(Plugin plugin, ILogger logger) + : IRequestHandler +{ + public async Task Handle(SendConnectionRequestRequest request, CancellationToken cancellationToken) + { + logger.Information("Sending connection request for {StationId} to {Callsign}", request.StationId, request.Callsign); + + if (plugin.ConnectionManager is null || !plugin.ConnectionManager.IsConnected) + { + logger.Warning("Not connected to server"); + return; + } + + await plugin.ConnectionManager.RequestConnection( + request.Callsign, + request.StationId, + cancellationToken); + } +} diff --git a/source/CPDLCPlugin/Server/SignalRConnectionManager.cs b/source/CPDLCPlugin/Server/SignalRConnectionManager.cs index 618d1fb..ba93224 100644 --- a/source/CPDLCPlugin/Server/SignalRConnectionManager.cs +++ b/source/CPDLCPlugin/Server/SignalRConnectionManager.cs @@ -180,6 +180,16 @@ await connection.InvokeAsync( cancellationToken: cancellationToken); } + public async Task RequestConnection(string callsign, string stationId, CancellationToken cancellationToken) + { + var connection = GetConnectedOrThrow(); + await connection.InvokeAsync( + "RequestConnection", + callsign, + stationId, + cancellationToken); + } + public async Task GetConnectedAircraft(CancellationToken cancellationToken) { var connection = GetConnectedOrThrow(); diff --git a/source/CPDLCPlugin/ViewModels/ConnectionRequestViewModel.cs b/source/CPDLCPlugin/ViewModels/ConnectionRequestViewModel.cs new file mode 100644 index 0000000..50aaced --- /dev/null +++ b/source/CPDLCPlugin/ViewModels/ConnectionRequestViewModel.cs @@ -0,0 +1,53 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CPDLCPlugin.Messages; +using MediatR; + +namespace CPDLCPlugin.ViewModels; + +public partial class ConnectionRequestViewModel : ObservableObject +{ + readonly IMediator _mediator; + readonly IErrorReporter _errorReporter; + readonly IWindowHandle _windowHandle; + + [ObservableProperty] string callsign; + [ObservableProperty] string[] availableStationIdentifiers; + [ObservableProperty] string selectedStationIdentifier; + + public ConnectionRequestViewModel( + IMediator mediator, + IErrorReporter errorReporter, + IWindowHandle windowHandle, + string callsign, + string[] availableStationIdentifiers, + string selectedStationIdentifier) + { + _mediator = mediator; + _errorReporter = errorReporter; + _windowHandle = windowHandle; + Callsign = callsign; + AvailableStationIdentifiers = availableStationIdentifiers; + SelectedStationIdentifier = selectedStationIdentifier; + } + + [RelayCommand] + async Task Confirm() + { + try + { + await _mediator.Send(new SendConnectionRequestRequest(Callsign, SelectedStationIdentifier)); + _windowHandle.Close(); + } + catch (Exception e) + { + _errorReporter.ReportError(e); + } + } + + [RelayCommand] + void Cancel() + { + _windowHandle.Close(); + } +} diff --git a/source/CPDLCPlugin/Windows/ConnectionRequestWindow.xaml b/source/CPDLCPlugin/Windows/ConnectionRequestWindow.xaml new file mode 100644 index 0000000..1e3c123 --- /dev/null +++ b/source/CPDLCPlugin/Windows/ConnectionRequestWindow.xaml @@ -0,0 +1,78 @@ + + + + + + + + + +