diff --git a/App/App.csproj b/App/App.csproj new file mode 100644 index 0000000..7a9ec8f --- /dev/null +++ b/App/App.csproj @@ -0,0 +1,48 @@ + + + WinExe + net8.0-windows10.0.19041.0 + 10.0.17763.0 + Coder.Desktop.App + app.manifest + x86;x64;ARM64 + win-x86;win-x64;win-arm64 + true + enable + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + MSBuild:Compile + + + + + + MSBuild:Compile + + + + + + False + True + False + True + + diff --git a/App/App.xaml b/App/App.xaml new file mode 100644 index 0000000..a5b6d8b --- /dev/null +++ b/App/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/App/App.xaml.cs b/App/App.xaml.cs new file mode 100644 index 0000000..4fbde75 --- /dev/null +++ b/App/App.xaml.cs @@ -0,0 +1,29 @@ +using Microsoft.UI.Xaml; + +namespace Coder.Desktop.App; + +public partial class App : Application +{ + private TrayWindow? TrayWindow; + + public App() + { + InitializeComponent(); + } + + private bool HandleClosedEvents { get; } = true; + + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + TrayWindow = new TrayWindow(); + TrayWindow.Closed += (sender, args) => + { + // TODO: wire up HandleClosedEvents properly + if (HandleClosedEvents) + { + args.Handled = true; + TrayWindow.AppWindow.Hide(); + } + }; + } +} diff --git a/App/Assets/coder_icon_32_dark.ico b/App/Assets/coder_icon_32_dark.ico new file mode 100644 index 0000000..4eaa1bb Binary files /dev/null and b/App/Assets/coder_icon_32_dark.ico differ diff --git a/App/Assets/coder_icon_32_light.ico b/App/Assets/coder_icon_32_light.ico new file mode 100644 index 0000000..1fc307f Binary files /dev/null and b/App/Assets/coder_icon_32_light.ico differ diff --git a/App/HorizontalRule.xaml b/App/HorizontalRule.xaml new file mode 100644 index 0000000..706e9b7 --- /dev/null +++ b/App/HorizontalRule.xaml @@ -0,0 +1,16 @@ + + + + + + diff --git a/App/HorizontalRule.xaml.cs b/App/HorizontalRule.xaml.cs new file mode 100644 index 0000000..ffb86dc --- /dev/null +++ b/App/HorizontalRule.xaml.cs @@ -0,0 +1,11 @@ +using Microsoft.UI.Xaml.Controls; + +namespace Coder.Desktop.App; + +public sealed partial class HorizontalRule : UserControl +{ + public HorizontalRule() + { + InitializeComponent(); + } +} diff --git a/App/TrayIcon.xaml b/App/TrayIcon.xaml new file mode 100644 index 0000000..7f5dede --- /dev/null +++ b/App/TrayIcon.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/App/TrayIcon.xaml.cs b/App/TrayIcon.xaml.cs new file mode 100644 index 0000000..db62e79 --- /dev/null +++ b/App/TrayIcon.xaml.cs @@ -0,0 +1,45 @@ +using System.Diagnostics; +using System.Windows.Input; +using Windows.UI.ViewManagement; +using DependencyPropertyGenerator; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Imaging; + +namespace Coder.Desktop.App; + +[DependencyProperty("OpenCommand")] +[DependencyProperty("ExitCommand")] +public sealed partial class TrayIcon : UserControl +{ + private readonly UISettings _uiSettings = new(); + + public TrayIcon() + { + InitializeComponent(); + _uiSettings.ColorValuesChanged += OnColorValuesChanged; + UpdateTrayIconBasedOnTheme(); + } + + private void OnColorValuesChanged(UISettings sender, object args) + { + DispatcherQueue.TryEnqueue(UpdateTrayIconBasedOnTheme); + } + + private void UpdateTrayIconBasedOnTheme() + { + var currentTheme = Application.Current.RequestedTheme; + Debug.WriteLine("Theme update requested, found theme: " + currentTheme); + + switch (currentTheme) + { + case ApplicationTheme.Dark: + TaskbarIcon.IconSource = (BitmapImage)Resources["IconDarkTheme"]; + break; + case ApplicationTheme.Light: + default: + TaskbarIcon.IconSource = (BitmapImage)Resources["IconLightTheme"]; + break; + } + } +} diff --git a/App/TrayWindow.xaml b/App/TrayWindow.xaml new file mode 100644 index 0000000..a779bb5 --- /dev/null +++ b/App/TrayWindow.xaml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/App/TrayWindow.xaml.cs b/App/TrayWindow.xaml.cs new file mode 100644 index 0000000..9e172b4 --- /dev/null +++ b/App/TrayWindow.xaml.cs @@ -0,0 +1,323 @@ +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Windows.ApplicationModel.DataTransfer; +using Windows.Foundation; +using Windows.Graphics; +using Windows.System; +using Windows.UI; +using Windows.UI.Core; +using CommunityToolkit.Mvvm.Input; +using Microsoft.UI; +using Microsoft.UI.Input; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Documents; +using Microsoft.UI.Xaml.Media; +using WinRT.Interop; +using WindowActivatedEventArgs = Microsoft.UI.Xaml.WindowActivatedEventArgs; + +namespace Coder.Desktop.App; + +public enum AgentStatus +{ + Green, + Red, + Gray, +} + +public partial class Agent +{ + public required string Hostname { get; set; } // without suffix + public required string Suffix { get; set; } + public AgentStatus Status { get; set; } + + public Brush StatusColor => Status switch + { + AgentStatus.Green => new SolidColorBrush(Color.FromArgb(255, 52, 199, 89)), + AgentStatus.Red => new SolidColorBrush(Color.FromArgb(255, 255, 59, 48)), + _ => new SolidColorBrush(Color.FromArgb(255, 142, 142, 147)), + }; + + [RelayCommand] + private void AgentHostnameButton_Click() + { + try + { + Process.Start(new ProcessStartInfo + { + // TODO: this should probably be more robust instead of just joining strings + FileName = "http://" + Hostname + Suffix, + UseShellExecute = true, + }); + } + catch + { + // TODO: log (notify?) + } + } + + [RelayCommand] + private void AgentHostnameCopyButton_Click(object parameter) + { + var dataPackage = new DataPackage + { + RequestedOperation = DataPackageOperation.Copy, + }; + dataPackage.SetText(Hostname + Suffix); + Clipboard.SetContent(dataPackage); + + if (parameter is not FrameworkElement frameworkElement) return; + + var flyout = new Flyout + { + Content = new TextBlock + { + Text = "DNS Copied", + Margin = new Thickness(4), + }, + }; + FlyoutBase.SetAttachedFlyout(frameworkElement, flyout); + FlyoutBase.ShowAttachedFlyout(frameworkElement); + } + + public void AgentHostnameText_OnLoaded(object sender, RoutedEventArgs e) + { + if (sender is not TextBlock textBlock) return; + textBlock.Inlines.Clear(); + textBlock.Inlines.Add(new Run + { + Text = Hostname, + Foreground = + (SolidColorBrush)Application.Current.Resources.ThemeDictionaries[ + "DefaultTextForegroundThemeBrush"], + }); + textBlock.Inlines.Add(new Run + { + Text = Suffix, + Foreground = + (SolidColorBrush)Application.Current.Resources.ThemeDictionaries[ + "SystemControlForegroundBaseMediumBrush"], + }); + } +} + +public sealed partial class TrayWindow : Window +{ + private const int WIDTH = 300; + + private NativeApi.POINT? _lastActivatePosition; + + public ObservableCollection Agents = + [ + new() + { + Hostname = "coder2", + Suffix = ".coder", + Status = AgentStatus.Green, + }, + new() + { + Hostname = "coder3", + Suffix = ".coder", + Status = AgentStatus.Red, + }, + new() + { + Hostname = "coder4", + Suffix = ".coder", + Status = AgentStatus.Gray, + }, + new() + { + Hostname = "superlongworkspacenamewhyisitsolong", + Suffix = ".coder", + Status = AgentStatus.Gray, + }, + ]; + + public TrayWindow() + { + InitializeComponent(); + AppWindow.Hide(); + SystemBackdrop = new DesktopAcrylicBackdrop(); + Activated += Window_Activated; + + // Setting OpenCommand and ExitCommand directly in the .xaml doesn't seem to work for whatever reason. + TrayIcon.OpenCommand = Tray_OpenCommand; + TrayIcon.ExitCommand = Tray_ExitCommand; + + if (Content is FrameworkElement frameworkElement) + frameworkElement.SizeChanged += Content_SizeChanged; + else + throw new Exception("Failed to get Content as FrameworkElement for window"); + + // Hide the title bar and buttons. WinUi 3 provides a method to do this with + // `ExtendsContentIntoTitleBar = true;`, but it automatically adds emulated title bar buttons that cannot be + // removed. + if (AppWindow.Presenter is not OverlappedPresenter presenter) + throw new Exception("Failed to get OverlappedPresenter for window"); + presenter.IsMaximizable = false; + presenter.IsMinimizable = false; + presenter.IsResizable = false; + presenter.IsAlwaysOnTop = true; + presenter.SetBorderAndTitleBar(true, false); + AppWindow.IsShownInSwitchers = false; + + // Ensure the corner is rounded. + var windowHandle = Win32Interop.GetWindowFromWindowId(AppWindow.Id); + var value = 2; + var result = NativeApi.DwmSetWindowAttribute(windowHandle, 33, ref value, Marshal.SizeOf()); + if (result != 0) throw new Exception("Failed to set window corner preference"); + } + + private void Content_SizeChanged(object sender, SizeChangedEventArgs e) + { + ResizeWindow(); + MoveWindow(); + } + + private void ResizeWindow() + { + if (Content is not FrameworkElement content) + throw new Exception("Failed to get Content as FrameworkElement for window"); + + // Measure the desired size of the content + content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + var desiredSize = content.DesiredSize; + + // Adjust the AppWindow size + var scale = GetDisplayScale(); + var height = (int)(desiredSize.Height * scale); + AppWindow.Resize(new SizeInt32(WIDTH, height)); + } + + private double GetDisplayScale() + { + var hwnd = WindowNative.GetWindowHandle(this); + var dpi = NativeApi.GetDpiForWindow(hwnd); + if (dpi == 0) return 1; // assume scale of 1 + return dpi / 96.0; // 96 DPI == 1 + } + + public void MoveResizeAndActivate() + { + SaveCursorPos(); + ResizeWindow(); + MoveWindow(); + AppWindow.Show(); + NativeApi.SetForegroundWindow(WindowNative.GetWindowHandle(this)); + } + + private void SaveCursorPos() + { + var res = NativeApi.GetCursorPos(out var cursorPosition); + if (res) + _lastActivatePosition = cursorPosition; + else + // When the cursor position is null, we will spawn the window in + // the bottom right corner of the primary display. + // TODO: log(?) an error when this happens + _lastActivatePosition = null; + } + + private void MoveWindow() + { + AppWindow.Move(GetWindowPosition()); + } + + private PointInt32 GetWindowPosition() + { + var height = AppWindow.Size.Height; + var cursorPosition = _lastActivatePosition; + if (cursorPosition is null) + { + var primaryWorkArea = DisplayArea.Primary.WorkArea; + return new PointInt32( + primaryWorkArea.Width - WIDTH, + primaryWorkArea.Height - height + ); + } + + // Spawn the window to the top right of the cursor. + var x = cursorPosition.Value.X + 10; + var y = cursorPosition.Value.Y - 10 - height; + + var workArea = DisplayArea.GetFromPoint( + new PointInt32(cursorPosition.Value.X, cursorPosition.Value.Y), + DisplayAreaFallback.Primary + ).WorkArea; + + // Adjust if the window goes off the right edge of the display. + if (x + WIDTH > workArea.X + workArea.Width) x = workArea.X + workArea.Width - WIDTH; + + // Adjust if the window goes off the bottom edge of the display. + if (y + height > workArea.Y + workArea.Height) y = workArea.Y + workArea.Height - height; + + // Adjust if the window goes off the left edge of the display (somehow). + if (x < workArea.X) x = workArea.X; + + // Adjust if the window goes off the top edge of the display (somehow). + if (y < workArea.Y) y = workArea.Y; + + return new PointInt32(x, y); + } + + private void Window_Activated(object sender, WindowActivatedEventArgs e) + { + // Close the window as soon as it loses focus. + if (e.WindowActivationState == WindowActivationState.Deactivated +#if DEBUG + // In DEBUG, holding SHIFT is required to have the window close when it loses focus. + && InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down) +#endif + ) + AppWindow.Hide(); + } + + private void ButtonBase_OnClick(object sender, RoutedEventArgs e) + { + Agents.Add(new Agent + { + Hostname = "cool", + Suffix = ".coder", + Status = AgentStatus.Gray, + }); + } + + [RelayCommand] + private void Tray_Open() + { + MoveResizeAndActivate(); + } + + [RelayCommand] + private void Tray_Exit() + { + // TODO: implement exit + } + + public class NativeApi + { + [DllImport("dwmapi.dll")] + public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attribute, ref int value, int size); + + [DllImport("user32.dll")] + public static extern bool GetCursorPos(out POINT lpPoint); + + [DllImport("user32.dll")] + public static extern bool SetForegroundWindow(IntPtr hwnd); + + [DllImport("user32.dll")] + public static extern int GetDpiForWindow(IntPtr hwnd); + + public struct POINT + { + public int X; + public int Y; + } + } +} diff --git a/App/app.manifest b/App/app.manifest new file mode 100644 index 0000000..28bdcff --- /dev/null +++ b/App/app.manifest @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + PerMonitorV2 + + + diff --git a/Coder.Desktop.sln b/Coder.Desktop.sln index c5fc598..e25c8fa 100644 --- a/Coder.Desktop.sln +++ b/Coder.Desktop.sln @@ -1,53 +1,192 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Vpn", "Vpn\Vpn.csproj", "{B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}" +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35707.178 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vpn", "Vpn\Vpn.csproj", "{B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Vpn.Proto", "Vpn.Proto\Vpn.Proto.csproj", "{318E78BB-E6AD-410F-8F3F-B680F6880293}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vpn.Proto", "Vpn.Proto\Vpn.Proto.csproj", "{318E78BB-E6AD-410F-8F3F-B680F6880293}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Vpn.Service", "Vpn.Service\Vpn.Service.csproj", "{51B91794-0A2A-4F84-9935-8E17DD2AB260}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vpn.Service", "Vpn.Service\Vpn.Service.csproj", "{51B91794-0A2A-4F84-9935-8E17DD2AB260}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Tests.Vpn", "Tests.Vpn\Tests.Vpn.csproj", "{D247B2E7-38A0-4A69-A710-7E8FAA7B807E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Vpn", "Tests.Vpn\Tests.Vpn.csproj", "{D247B2E7-38A0-4A69-A710-7E8FAA7B807E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Tests.Vpn.Proto", "Tests.Vpn.Proto\Tests.Vpn.Proto.csproj", "{AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Vpn.Proto", "Tests.Vpn.Proto\Tests.Vpn.Proto.csproj", "{AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Tests.Vpn.Service", "Tests.Vpn.Service\Tests.Vpn.Service.csproj", "{D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Vpn.Service", "Tests.Vpn.Service\Tests.Vpn.Service.csproj", "{D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.CoderSdk", "CoderSdk\CoderSdk.csproj", "{A3D2B2B3-A051-46BD-A190-5487A9F24C28}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoderSdk", "CoderSdk\CoderSdk.csproj", "{A3D2B2B3-A051-46BD-A190-5487A9F24C28}" +EndProject +Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Package", "Package\Package.wapproj", "{C184988D-56E0-451F-B6A1-E5FE0405C80B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "App\App.csproj", "{800C7E2D-0D86-4554-9903-B879DA6FA2CE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Debug|ARM64.Build.0 = Debug|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Debug|x64.ActiveCfg = Debug|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Debug|x64.Build.0 = Debug|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Debug|x86.ActiveCfg = Debug|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Debug|x86.Build.0 = Debug|Any CPU {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Release|Any CPU.ActiveCfg = Release|Any CPU {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Release|Any CPU.Build.0 = Release|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Release|ARM64.ActiveCfg = Release|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Release|ARM64.Build.0 = Release|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Release|x64.ActiveCfg = Release|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Release|x64.Build.0 = Release|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Release|x86.ActiveCfg = Release|Any CPU + {B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}.Release|x86.Build.0 = Release|Any CPU {318E78BB-E6AD-410F-8F3F-B680F6880293}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {318E78BB-E6AD-410F-8F3F-B680F6880293}.Debug|Any CPU.Build.0 = Debug|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Debug|ARM64.Build.0 = Debug|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Debug|x64.ActiveCfg = Debug|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Debug|x64.Build.0 = Debug|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Debug|x86.ActiveCfg = Debug|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Debug|x86.Build.0 = Debug|Any CPU {318E78BB-E6AD-410F-8F3F-B680F6880293}.Release|Any CPU.ActiveCfg = Release|Any CPU {318E78BB-E6AD-410F-8F3F-B680F6880293}.Release|Any CPU.Build.0 = Release|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Release|ARM64.ActiveCfg = Release|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Release|ARM64.Build.0 = Release|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Release|x64.ActiveCfg = Release|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Release|x64.Build.0 = Release|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Release|x86.ActiveCfg = Release|Any CPU + {318E78BB-E6AD-410F-8F3F-B680F6880293}.Release|x86.Build.0 = Release|Any CPU {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Debug|ARM64.Build.0 = Debug|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Debug|x64.ActiveCfg = Debug|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Debug|x64.Build.0 = Debug|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Debug|x86.ActiveCfg = Debug|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Debug|x86.Build.0 = Debug|Any CPU {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Release|Any CPU.ActiveCfg = Release|Any CPU {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Release|Any CPU.Build.0 = Release|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Release|ARM64.ActiveCfg = Release|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Release|ARM64.Build.0 = Release|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Release|x64.ActiveCfg = Release|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Release|x64.Build.0 = Release|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Release|x86.ActiveCfg = Release|Any CPU + {51B91794-0A2A-4F84-9935-8E17DD2AB260}.Release|x86.Build.0 = Release|Any CPU {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Debug|ARM64.Build.0 = Debug|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Debug|x64.ActiveCfg = Debug|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Debug|x64.Build.0 = Debug|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Debug|x86.ActiveCfg = Debug|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Debug|x86.Build.0 = Debug|Any CPU {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Release|Any CPU.ActiveCfg = Release|Any CPU {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Release|Any CPU.Build.0 = Release|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Release|ARM64.ActiveCfg = Release|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Release|ARM64.Build.0 = Release|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Release|x64.ActiveCfg = Release|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Release|x64.Build.0 = Release|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Release|x86.ActiveCfg = Release|Any CPU + {D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Release|x86.Build.0 = Release|Any CPU {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Debug|ARM64.Build.0 = Debug|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Debug|x64.Build.0 = Debug|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Debug|x86.ActiveCfg = Debug|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Debug|x86.Build.0 = Debug|Any CPU {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Release|Any CPU.Build.0 = Release|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Release|ARM64.ActiveCfg = Release|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Release|ARM64.Build.0 = Release|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Release|x64.ActiveCfg = Release|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Release|x64.Build.0 = Release|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Release|x86.ActiveCfg = Release|Any CPU + {AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Release|x86.Build.0 = Release|Any CPU {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Debug|ARM64.Build.0 = Debug|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Debug|x64.ActiveCfg = Debug|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Debug|x64.Build.0 = Debug|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Debug|x86.ActiveCfg = Debug|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Debug|x86.Build.0 = Debug|Any CPU {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Release|Any CPU.ActiveCfg = Release|Any CPU {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Release|Any CPU.Build.0 = Release|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Release|ARM64.ActiveCfg = Release|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Release|ARM64.Build.0 = Release|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Release|x64.ActiveCfg = Release|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Release|x64.Build.0 = Release|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Release|x86.ActiveCfg = Release|Any CPU + {D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Release|x86.Build.0 = Release|Any CPU {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Debug|ARM64.Build.0 = Debug|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Debug|x64.ActiveCfg = Debug|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Debug|x64.Build.0 = Debug|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Debug|x86.ActiveCfg = Debug|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Debug|x86.Build.0 = Debug|Any CPU {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|Any CPU.Build.0 = Release|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|ARM64.ActiveCfg = Release|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|ARM64.Build.0 = Release|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|x64.ActiveCfg = Release|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|x64.Build.0 = Release|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|x86.ActiveCfg = Release|Any CPU + {A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|x86.Build.0 = Release|Any CPU + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|Any CPU.ActiveCfg = Debug|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|Any CPU.Build.0 = Debug|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|Any CPU.Deploy.0 = Debug|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|ARM64.Build.0 = Debug|ARM64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x64.ActiveCfg = Debug|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x64.Build.0 = Debug|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x64.Deploy.0 = Debug|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x86.ActiveCfg = Debug|x86 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x86.Build.0 = Debug|x86 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x86.Deploy.0 = Debug|x86 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|Any CPU.ActiveCfg = Release|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|Any CPU.Build.0 = Release|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|Any CPU.Deploy.0 = Release|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|ARM64.ActiveCfg = Release|ARM64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|ARM64.Build.0 = Release|ARM64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|ARM64.Deploy.0 = Release|ARM64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x64.ActiveCfg = Release|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x64.Build.0 = Release|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x64.Deploy.0 = Release|x64 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x86.ActiveCfg = Release|x86 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x86.Build.0 = Release|x86 + {C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x86.Deploy.0 = Release|x86 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|Any CPU.ActiveCfg = Debug|x64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|Any CPU.Build.0 = Debug|x64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|ARM64.Build.0 = Debug|ARM64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|x64.ActiveCfg = Debug|x64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|x64.Build.0 = Debug|x64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|x86.ActiveCfg = Debug|x86 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|x86.Build.0 = Debug|x86 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|Any CPU.ActiveCfg = Release|x64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|Any CPU.Build.0 = Release|x64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|ARM64.ActiveCfg = Release|ARM64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|ARM64.Build.0 = Release|ARM64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|x64.ActiveCfg = Release|x64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|x64.Build.0 = Release|x64 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|x86.ActiveCfg = Release|x86 + {800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/CoderSdk/CoderSdk.csproj b/CoderSdk/CoderSdk.csproj index 3a63532..1ca7d3c 100644 --- a/CoderSdk/CoderSdk.csproj +++ b/CoderSdk/CoderSdk.csproj @@ -1,6 +1,7 @@ - + + Coder.Desktop.CoderSdk net8.0 enable enable diff --git a/Package/Images/SplashScreen.scale-200.png b/Package/Images/SplashScreen.scale-200.png new file mode 100644 index 0000000..32f486a Binary files /dev/null and b/Package/Images/SplashScreen.scale-200.png differ diff --git a/Package/Images/Square150x150Logo.scale-200.png b/Package/Images/Square150x150Logo.scale-200.png new file mode 100644 index 0000000..53ee377 Binary files /dev/null and b/Package/Images/Square150x150Logo.scale-200.png differ diff --git a/Package/Images/Square44x44Logo.scale-200.png b/Package/Images/Square44x44Logo.scale-200.png new file mode 100644 index 0000000..f713bba Binary files /dev/null and b/Package/Images/Square44x44Logo.scale-200.png differ diff --git a/Package/Package.appxmanifest b/Package/Package.appxmanifest new file mode 100644 index 0000000..679c072 --- /dev/null +++ b/Package/Package.appxmanifest @@ -0,0 +1,52 @@ + + + + + + + + + + App (Package) + dean + Images\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package/Package.wapproj b/Package/Package.wapproj new file mode 100644 index 0000000..10fb751 --- /dev/null +++ b/Package/Package.wapproj @@ -0,0 +1,67 @@ + + + + 15.0 + + + + Debug + x86 + + + Release + x86 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ + App\ + + + + c184988d-56e0-451f-b6a1-e5fe0405c80b + 10.0.22621.0 + 10.0.17763.0 + net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback) + en-US + false + ..\App\App.csproj + + + + Designer + + + + + + + + + + True + Properties\PublishProfiles\win-$(Platform).pubxml + + + + + + + + \ No newline at end of file diff --git a/Tests.Vpn.Service/TestHttpServer.cs b/Tests.Vpn.Service/TestHttpServer.cs index d33697f..4129b0d 100644 --- a/Tests.Vpn.Service/TestHttpServer.cs +++ b/Tests.Vpn.Service/TestHttpServer.cs @@ -15,8 +15,6 @@ public class TestHttpServer : IDisposable private readonly HttpListener _listener; private readonly Thread _listenerThread; - public string BaseUrl { get; private set; } - public TestHttpServer(Action handler) : this(ctx => { handler(ctx); @@ -77,6 +75,8 @@ public TestHttpServer(Func handler) _listenerThread.Start(); } + public string BaseUrl { get; private set; } + public void Dispose() { _cts.Cancel(); diff --git a/Tests.Vpn/SpeakerTest.cs b/Tests.Vpn/SpeakerTest.cs index 51950f7..0b1552b 100644 --- a/Tests.Vpn/SpeakerTest.cs +++ b/Tests.Vpn/SpeakerTest.cs @@ -16,6 +16,13 @@ internal class FailableStream : Stream private readonly TaskCompletionSource _writeTcs = new(); + public FailableStream(Stream inner, Exception? writeException, Exception? readException) + { + _inner = inner; + if (writeException != null) _writeTcs.SetException(writeException); + if (readException != null) _readTcs.SetException(readException); + } + public override bool CanRead => _inner.CanRead; public override bool CanSeek => _inner.CanSeek; public override bool CanWrite => _inner.CanWrite; @@ -27,13 +34,6 @@ public override long Position set => _inner.Position = value; } - public FailableStream(Stream inner, Exception? writeException, Exception? readException) - { - _inner = inner; - if (writeException != null) _writeTcs.SetException(writeException); - if (readException != null) _readTcs.SetException(readException); - } - public void SetWriteException(Exception ex) { _writeTcs.SetException(ex); diff --git a/Vpn.Proto/RpcHeader.cs b/Vpn.Proto/RpcHeader.cs index cf7ffcc..c81eb1d 100644 --- a/Vpn.Proto/RpcHeader.cs +++ b/Vpn.Proto/RpcHeader.cs @@ -9,9 +9,6 @@ public class RpcHeader { private const string Preamble = "codervpn"; - public string Role { get; } - public RpcVersionList VersionList { get; } - /// Role of the peer /// Version of the peer public RpcHeader(string role, RpcVersionList versionList) @@ -20,6 +17,9 @@ public RpcHeader(string role, RpcVersionList versionList) VersionList = versionList; } + public string Role { get; } + public RpcVersionList VersionList { get; } + /// /// Parse a header string into a SpeakerHeader. /// diff --git a/Vpn.Proto/RpcMessage.cs b/Vpn.Proto/RpcMessage.cs index bfe4d82..fc1af11 100644 --- a/Vpn.Proto/RpcMessage.cs +++ b/Vpn.Proto/RpcMessage.cs @@ -6,12 +6,12 @@ namespace Coder.Desktop.Vpn.Proto; [AttributeUsage(AttributeTargets.Class, Inherited = false)] public class RpcRoleAttribute : Attribute { - public string Role { get; } - public RpcRoleAttribute(string role) { Role = role; } + + public string Role { get; } } /// diff --git a/Vpn.Proto/RpcVersion.cs b/Vpn.Proto/RpcVersion.cs index 574768d..8eb0de0 100644 --- a/Vpn.Proto/RpcVersion.cs +++ b/Vpn.Proto/RpcVersion.cs @@ -7,9 +7,6 @@ public class RpcVersion { public static readonly RpcVersion Current = new(1, 0); - public ulong Major { get; } - public ulong Minor { get; } - /// The major version of the peer /// The minor version of the peer public RpcVersion(ulong major, ulong minor) @@ -18,6 +15,9 @@ public RpcVersion(ulong major, ulong minor) Minor = minor; } + public ulong Major { get; } + public ulong Minor { get; } + /// /// Parse a string in the format "major.minor" into an ApiVersion. /// diff --git a/Vpn.Service/Downloader.cs b/Vpn.Service/Downloader.cs index 83eda24..ea4c587 100644 --- a/Vpn.Service/Downloader.cs +++ b/Vpn.Service/Downloader.cs @@ -42,13 +42,13 @@ public class AuthenticodeDownloadValidator : IDownloadValidator { private readonly string _expectedName; - public static AuthenticodeDownloadValidator Coder => new("Coder Technologies Inc."); - public AuthenticodeDownloadValidator(string expectedName) { _expectedName = expectedName; } + public static AuthenticodeDownloadValidator Coder => new("Coder Technologies Inc."); + public async Task ValidateAsync(string path, CancellationToken ct = default) { FileSignatureInfo fileSigInfo; @@ -187,13 +187,6 @@ public class DownloadTask public readonly HttpRequestMessage Request; public readonly string TempDestinationPath; - public ulong? TotalBytes { get; private set; } - public ulong BytesRead { get; private set; } - public Task Task { get; private set; } = null!; // Set in EnsureStartedAsync - - public double? Progress => TotalBytes == null ? null : (double)BytesRead / TotalBytes.Value; - public bool IsCompleted => Task.IsCompleted; - internal DownloadTask(ILogger logger, HttpRequestMessage req, string destinationPath, IDownloadValidator validator) { _logger = logger; @@ -216,6 +209,13 @@ internal DownloadTask(ILogger logger, HttpRequestMessage req, string destination ".download-" + Path.GetRandomFileName()); } + public ulong? TotalBytes { get; private set; } + public ulong BytesRead { get; private set; } + public Task Task { get; private set; } = null!; // Set in EnsureStartedAsync + + public double? Progress => TotalBytes == null ? null : (double)BytesRead / TotalBytes.Value; + public bool IsCompleted => Task.IsCompleted; + internal async Task EnsureStartedAsync(CancellationToken ct = default) { using var _ = await _semaphore.LockAsync(ct); diff --git a/Vpn/Speaker.cs b/Vpn/Speaker.cs index 4c6ef3c..899c346 100644 --- a/Vpn/Speaker.cs +++ b/Vpn/Speaker.cs @@ -27,14 +27,6 @@ public class ReplyableRpcMessage : RpcMessage private readonly TR _message; private readonly Speaker _speaker; - public override RPC? RpcField - { - get => _message.RpcField; - set => _message.RpcField = value; - } - - public override TR Message => _message; - /// Speaker to use for sending reply /// Original received message public ReplyableRpcMessage(Speaker speaker, TR message) @@ -43,6 +35,14 @@ public ReplyableRpcMessage(Speaker speaker, TR message) _message = message; } + public override RPC? RpcField + { + get => _message.RpcField; + set => _message.RpcField = value; + } + + public override TR Message => _message; + public override void Validate() { _message.Validate(); diff --git a/Vpn/Utilities/BidirectionalPipe.cs b/Vpn/Utilities/BidirectionalPipe.cs index 72e633b..0792ce8 100644 --- a/Vpn/Utilities/BidirectionalPipe.cs +++ b/Vpn/Utilities/BidirectionalPipe.cs @@ -10,6 +10,14 @@ public class BidirectionalPipe : Stream private readonly Stream _reader; private readonly Stream _writer; + /// The stream to perform reads from + /// The stream to write data to + public BidirectionalPipe(Stream reader, Stream writer) + { + _reader = reader; + _writer = writer; + } + public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => true; @@ -21,14 +29,6 @@ public override long Position set => throw new NotImplementedException("BidirectionalPipe does not support setting position"); } - /// The stream to perform reads from - /// The stream to write data to - public BidirectionalPipe(Stream reader, Stream writer) - { - _reader = reader; - _writer = writer; - } - /// /// Creates a new pair of BidirectionalPipes that are connected to each other using buffered in-memory pipes. ///