This commit is contained in:
Myx
2025-03-19 01:16:12 +01:00
commit 83a80b4b0f
28 changed files with 893 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.idea
.vs
bin
obj

16
App.xaml Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Application
x:Class="BeetleWire_UI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BeetleWire_UI">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
</ResourceDictionary>
</Application.Resources>
</Application>

49
App.xaml.cs Normal file
View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.UI.Xaml.Shapes;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BeetleWire_UI;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
}
/// <summary>
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
m_window = new MainWindow();
m_window.Activate();
}
private Window? m_window;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

BIN
Assets/StoreLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

79
BeetleWire_UI.csproj Normal file
View File

@@ -0,0 +1,79 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>BeetleWire_UI</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
<EnableMsixTooling>true</EnableMsixTooling>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="Pages\ClientPage.xaml" />
<None Remove="Pages\ConnectionsPage.xaml" />
<None Remove="Pages\ServerPage.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<!--
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored.
-->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250310001" />
</ItemGroup>
<ItemGroup>
<Page Update="Pages\ConnectionsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\ServerPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\ClientPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!--
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
Explorer "Package and Publish" context menu entry to be enabled for this project even if
the Windows App SDK Nuget package has not yet been restored.
-->
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
<!-- Publish Properties -->
<PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
</PropertyGroup>
</Project>

24
BeetleWire_UI.csproj.user Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
<ItemGroup>
<None Update="App.xaml">
<SubType>Designer</SubType>
</None>
<None Update="MainWindow.xaml">
<SubType>Designer</SubType>
</None>
<None Update="Package.appxmanifest">
<SubType>Designer</SubType>
</None>
<Page Update="Pages\ConnectionsPage.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="Pages\ServerPage.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="Pages\ClientPage.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
</Project>

40
BeetleWire_UI.sln Normal file
View File

@@ -0,0 +1,40 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35527.113 d17.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BeetleWire_UI", "BeetleWire_UI.csproj", "{84C136C8-D156-4010-B0BF-2DD13FA5E617}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Debug|ARM64.ActiveCfg = Debug|ARM64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Debug|ARM64.Build.0 = Debug|ARM64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Debug|ARM64.Deploy.0 = Debug|ARM64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Debug|x64.ActiveCfg = Debug|x64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Debug|x64.Build.0 = Debug|x64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Debug|x64.Deploy.0 = Debug|x64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Debug|x86.ActiveCfg = Debug|x86
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Debug|x86.Build.0 = Debug|x86
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Debug|x86.Deploy.0 = Debug|x86
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Release|ARM64.ActiveCfg = Release|ARM64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Release|ARM64.Build.0 = Release|ARM64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Release|ARM64.Deploy.0 = Release|ARM64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Release|x64.ActiveCfg = Release|x64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Release|x64.Build.0 = Release|x64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Release|x64.Deploy.0 = Release|x64
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Release|x86.ActiveCfg = Release|x86
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Release|x86.Build.0 = Release|x86
{84C136C8-D156-4010-B0BF-2DD13FA5E617}.Release|x86.Deploy.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

33
MainWindow.xaml Normal file
View File

@@ -0,0 +1,33 @@
<Window
x:Class="BeetleWire_UI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BeetleWire_UI"
xmlns:pages="using:BeetleWire_UI.Pages"
Title="File Sharing App"
>
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<Grid>
<NavigationView x:Name="NavView" PaneDisplayMode="Left" SelectionChanged="NavView_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem Content="Client" Tag="client" Icon="Contact"/>
<NavigationViewItem Content="Server" Tag="server" Icon="Globe"/>
<NavigationViewItem Content="Connections" Tag="connections" Icon="Link"/>
</NavigationView.MenuItems>
<Frame Padding="10 10 0 0" x:Name="ContentFrame"/>
</NavigationView>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="10">
<StackPanel x:Name="ServerStatusPanel" Orientation="Horizontal" Visibility="Collapsed">
<TextBlock Text="Server Running" Margin="0,0,5,0"/>
<Button Content="Stop Server" Click="StopServerButton_Click"/>
</StackPanel>
<StackPanel x:Name="ClientStatusPanel" Orientation="Horizontal" Visibility="Collapsed" Margin="10,0,0,0">
<TextBlock Text="Client Connected" Margin="0,0,5,0"/>
<Button Content="Disconnect Client" Click="DisconnectClientButton_Click"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>

59
MainWindow.xaml.cs Normal file
View File

@@ -0,0 +1,59 @@
using BeetleWire_UI.Pages;
using BeetleWire_UI.Services;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace BeetleWire_UI;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
ExtendsContentIntoTitleBar = true;
ContentFrame.Navigate(typeof(ClientPage), this);
}
private void NavView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
if (args.SelectedItemContainer is NavigationViewItem selectedItem)
{
var tag = selectedItem.Tag.ToString();
switch (tag)
{
case "client":
ContentFrame.Navigate(typeof(ClientPage), this);
break;
case "server":
ContentFrame.Navigate(typeof(ServerPage), this);
break;
case "connections":
ContentFrame.Navigate(typeof(ConnectionsPage));
break;
}
}
}
private void StopServerButton_Click(object sender, RoutedEventArgs e)
{
ConnectionManager.Instance.StopServer();
ServerStatusPanel.Visibility = Visibility.Collapsed;
}
private void DisconnectClientButton_Click(object sender, RoutedEventArgs e)
{
ConnectionManager.Instance.DisconnectClient();
ClientStatusPanel.Visibility = Visibility.Collapsed;
}
public void UpdateServerStatus(bool isRunning)
{
ServerStatusPanel.Visibility = isRunning ? Visibility.Visible : Visibility.Collapsed;
}
public void UpdateClientStatus(bool isConnected)
{
ClientStatusPanel.Visibility = isConnected ? Visibility.Visible : Visibility.Collapsed;
}
}

51
Package.appxmanifest Normal file
View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity
Name="e54914e5-0d53-4677-9130-c8893e271d14"
Publisher="CN=Ludvi"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="e54914e5-0d53-4677-9130-c8893e271d14" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>BeetleWire_UI</DisplayName>
<PublisherDisplayName>Ludvi</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="BeetleWire_UI"
Description="BeetleWire_UI"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

29
Pages/ClientPage.xaml Normal file
View File

@@ -0,0 +1,29 @@
<Page
x:Class="BeetleWire_UI.Pages.ClientPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BeetleWire_UI.Pages">
<Grid Padding="12">
<StackPanel>
<!-- Shared Folder Section -->
<TextBlock Text="Client" FontSize="20" FontWeight="Bold" Margin="0,0,0,12"/>
<TextBlock Text="Shared Folder Path:" FontWeight="Bold" />
<StackPanel Orientation="Horizontal">
<TextBox x:Name="SharedFolderPathTextBox" Width="500" Margin="0,0,12,0"/>
<Button Content="Set Folder" Click="SetFolderButton_Click"/>
</StackPanel>
<!-- Server Connection Section -->
<TextBlock Text="Server Address:"/>
<TextBox x:Name="ServerAddressTextBox" Text="127.0.0.1:1337" Margin="0,0,0,12"/>
<StackPanel Orientation="Horizontal">
<Button Content="Connect to Server" Click="ConnectButton_Click" Margin="0,0,0,12"/>
<Button Content="Save Connection" Click="SaveConnectionButton_Click" Margin="12,0,0,12"/>
</StackPanel>
<!-- File List and Download Section -->
<TextBlock Text="Available Files:"/>
<ListView x:Name="FilesListView" SelectionChanged="FilesListView_SelectionChanged" Height="200"/>
<ProgressBar x:Name="DownloadProgressBar" Height="20" Margin="0,12,0,0" Visibility="Collapsed"/>
<TextBlock x:Name="StatusTextBlock" Margin="0,12,0,0"/>
</StackPanel>
</Grid>
</Page>

170
Pages/ClientPage.xaml.cs Normal file
View File

@@ -0,0 +1,170 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Windows.Storage;
using BeetleWire_UI.Services;
using Microsoft.UI.Xaml.Navigation;
namespace BeetleWire_UI.Pages;
public sealed partial class ClientPage : Page
{
private ClientWebSocket _webSocket;
private CancellationTokenSource _cts = new CancellationTokenSource();
private string _sharedFolderPath;
private MainWindow _mainWindow;
public ClientPage()
{
this.InitializeComponent();
LoadSharedFolderPath();
EnsureSharedFolderExists();
}
private void LoadSharedFolderPath()
{
var localSettings = ApplicationData.Current.LocalSettings;
if (localSettings.Values.ContainsKey("SharedFolderPath"))
{
_sharedFolderPath = localSettings.Values["SharedFolderPath"].ToString();
}
else
{
_sharedFolderPath = System.IO.Path.Combine(Environment.CurrentDirectory, "Shared");
}
SharedFolderPathTextBox.Text = _sharedFolderPath;
}
private void SaveSharedFolderPath()
{
var localSettings = ApplicationData.Current.LocalSettings;
localSettings.Values["SharedFolderPath"] = _sharedFolderPath;
}
private void SetFolderButton_Click(object sender, RoutedEventArgs e)
{
_sharedFolderPath = SharedFolderPathTextBox.Text.Trim();
if (string.IsNullOrEmpty(_sharedFolderPath))
{
StatusTextBlock.Text = "Folder path cannot be empty.";
return;
}
SaveSharedFolderPath();
EnsureSharedFolderExists();
StatusTextBlock.Text = $"Shared folder set to: {_sharedFolderPath}";
}
private void EnsureSharedFolderExists()
{
if (!System.IO.Directory.Exists(_sharedFolderPath))
{
try
{
System.IO.Directory.CreateDirectory(_sharedFolderPath);
}
catch (Exception ex)
{
StatusTextBlock.Text = $"Failed to create folder: {ex.Message}";
}
}
}
private async void ConnectButton_Click(object sender, RoutedEventArgs e)
{
var serverAddress = ServerAddressTextBox.Text;
var uri = new Uri($"ws://{serverAddress}");
try
{
await ConnectionManager.Instance.ConnectClientAsync(uri, _cts.Token);
StatusTextBlock.Text = "Connected to server.";
var fileList = await ReceiveFileListAsync();
FilesListView.ItemsSource = fileList;
_mainWindow?.UpdateClientStatus(true);
}
catch (Exception ex)
{
StatusTextBlock.Text = $"Error connecting: {ex.Message}";
}
}
private async Task<List<string>> ReceiveFileListAsync()
{
var buffer = new ArraySegment<byte>(new byte[4096]);
WebSocketReceiveResult result = await _webSocket.ReceiveAsync(buffer, _cts.Token);
string jsonString = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);
var fileList = JsonSerializer.Deserialize<List<string>>(jsonString);
return fileList ?? new List<string>();
}
private async void FilesListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (FilesListView.SelectedItem is string fileName)
{
StatusTextBlock.Text = $"Downloading {fileName}...";
DownloadProgressBar.Visibility = Visibility.Visible;
DownloadProgressBar.Value = 0;
byte[] requestBytes = Encoding.UTF8.GetBytes(fileName);
await _webSocket.SendAsync(new ArraySegment<byte>(requestBytes), WebSocketMessageType.Text, true, _cts.Token);
byte[] fileData = await ReceiveFileDataAsync();
string filePath = System.IO.Path.Combine(_sharedFolderPath, fileName);
System.IO.File.WriteAllBytes(filePath, fileData);
StatusTextBlock.Text = $"File {fileName} downloaded to {_sharedFolderPath}.";
DownloadProgressBar.Visibility = Visibility.Collapsed;
}
}
private async Task<byte[]> ReceiveFileDataAsync()
{
var buffer = new ArraySegment<byte>(new byte[8192]);
using (var ms = new System.IO.MemoryStream())
{
WebSocketReceiveResult result;
do
{
result = await _webSocket.ReceiveAsync(buffer, _cts.Token);
ms.Write(buffer.Array, buffer.Offset, result.Count);
} while (!result.EndOfMessage);
return ms.ToArray();
}
}
private void SaveConnectionButton_Click(object sender, RoutedEventArgs e)
{
var serverAddress = ServerAddressTextBox.Text.Trim();
if (string.IsNullOrEmpty(serverAddress))
{
StatusTextBlock.Text = "Server address cannot be empty.";
return;
}
var localSettings = ApplicationData.Current.LocalSettings;
var connections = localSettings.Values["Connections"] as ApplicationDataCompositeValue ?? new ApplicationDataCompositeValue();
connections[serverAddress] = DateTime.Now.ToString();
localSettings.Values["Connections"] = connections;
StatusTextBlock.Text = $"Connection to {serverAddress} saved.";
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.Parameter is MainWindow mainWindow)
{
_mainWindow = mainWindow;
}
else
{
_mainWindow = null;
}
}
}

View File

@@ -0,0 +1,12 @@
<Page
x:Class="BeetleWire_UI.Pages.ConnectionsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BeetleWire_UI.Pages">
<Grid Padding="12">
<StackPanel>
<TextBlock Text="Saved Connections" FontSize="20" FontWeight="Bold" Margin="0,0,0,12"/>
<ListView x:Name="ConnectionsListView" Height="200"/>
</StackPanel>
</Grid>
</Page>

View File

@@ -0,0 +1,27 @@
using Windows.Storage;
using Microsoft.UI.Xaml.Controls;
namespace BeetleWire_UI.Pages;
public sealed partial class ConnectionsPage : Page
{
public ConnectionsPage()
{
this.InitializeComponent();
LoadConnections();
}
private void LoadConnections()
{
var localSettings = ApplicationData.Current.LocalSettings;
var connections = localSettings.Values["Connections"] as ApplicationDataCompositeValue;
if (connections != null)
{
foreach (var connection in connections)
{
ConnectionsListView.Items.Add($"{connection.Key} - {connection.Value}");
}
}
}
}

19
Pages/ServerPage.xaml Normal file
View File

@@ -0,0 +1,19 @@
<Page
x:Class="BeetleWire_UI.Pages.ServerPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BeetleWire_UI.Pages">
<Grid Padding="12">
<StackPanel>
<TextBlock Text="Server" FontSize="20" FontWeight="Bold" Margin="0,0,0,12"/>
<TextBlock Text="Listening on:"/>
<TextBox x:Name="ServerAddressTextBox" Text="localhost:1337" Margin="0,0,0,12" Width="300"/>
<TextBlock Text="Directory:"/>
<TextBox x:Name="ServerDirectoryTextBox" Text="C:\Shared" Margin="0,0,0,12" Width="300"/>
<Button Content="Start Server" Click="StartServerButton_Click" Width="200"/>
<ScrollViewer Margin="0,12,0,0" Height="200">
<TextBlock x:Name="LogTextBlock" TextWrapping="Wrap"/>
</ScrollViewer>
</StackPanel>
</Grid>
</Page>

161
Pages/ServerPage.xaml.cs Normal file
View File

@@ -0,0 +1,161 @@
using BeetleWire_UI.Services;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Navigation;
namespace BeetleWire_UI.Pages;
public sealed partial class ServerPage : Page
{
private HttpListener _listener;
private MainWindow _mainWindow;
public ServerPage()
{
this.InitializeComponent();
}
private void StartServerButton_Click(object sender, RoutedEventArgs e)
{
string serverAddress = ServerAddressTextBox.Text.Trim();
string dirPath = ServerDirectoryTextBox.Text.Trim();
if (!Directory.Exists(dirPath))
{
Log("Directory does not exist: " + dirPath);
return;
}
string prefix = $"http://{serverAddress}/";
try
{
ConnectionManager.Instance.StartServer(prefix);
Log($"Server listening on {serverAddress}");
// Update the main window status
_mainWindow.UpdateServerStatus(true);
}
catch (Exception ex)
{
Log("Failed to start listener: " + ex.Message);
}
}
private async Task RunServerLoopAsync(string dirPath, CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
// Wait for an incoming HTTP request.
var context = await _listener.GetContextAsync();
// Check if it's a WebSocket request.
if (context.Request.IsWebSocketRequest)
{
_ = ProcessWebSocketRequestAsync(context, dirPath, token);
}
else
{
context.Response.StatusCode = 400;
context.Response.Close();
}
}
}
catch (Exception ex)
{
Log("Server loop error: " + ex.Message);
}
}
private async Task ProcessWebSocketRequestAsync(HttpListenerContext context, string dirPath, CancellationToken token)
{
HttpListenerWebSocketContext wsContext = null;
try
{
wsContext = await context.AcceptWebSocketAsync(null);
Log("WebSocket connection established.");
}
catch (Exception ex)
{
Log("WebSocket handshake error: " + ex.Message);
context.Response.StatusCode = 500;
context.Response.Close();
return;
}
WebSocket webSocket = wsContext.WebSocket;
try
{
// List files in the directory.
List<string> fileList = new List<string>();
foreach (var file in Directory.EnumerateFiles(dirPath))
{
fileList.Add(Path.GetFileName(file));
}
string fileListJson = JsonSerializer.Serialize(fileList);
byte[] listBytes = Encoding.UTF8.GetBytes(fileListJson);
await webSocket.SendAsync(new ArraySegment<byte>(listBytes), WebSocketMessageType.Text, true, token);
Log("Sent file list.");
// Wait for the client to request a file.
byte[] recvBuffer = new byte[4096];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(recvBuffer), token);
if (result.MessageType == WebSocketMessageType.Text)
{
string requestedFile = Encoding.UTF8.GetString(recvBuffer, 0, result.Count);
Log($"Client requested file: {requestedFile}");
string filePath = Path.Combine(dirPath, requestedFile);
if (File.Exists(filePath))
{
byte[] fileBytes = await File.ReadAllBytesAsync(filePath, token);
await webSocket.SendAsync(new ArraySegment<byte>(fileBytes), WebSocketMessageType.Binary, true, token);
Log("Sent file data.");
}
else
{
Log("Requested file not found.");
}
}
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Done", token);
Log("WebSocket connection closed.");
}
catch (Exception ex)
{
Log("Error during WebSocket communication: " + ex.Message);
}
}
// Helper to update the log (on UI thread).
private void Log(string message)
{
DispatcherQueue.TryEnqueue(() => {
LogTextBlock.Text += $"{DateTime.Now:T} - {message}\n";
});
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.Parameter is MainWindow mainWindow)
{
_mainWindow = mainWindow;
}
else
{
_mainWindow = null;
}
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Platform>ARM64</Platform>
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Platform>x64</Platform>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Platform>x86</Platform>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,10 @@
{
"profiles": {
"BeetleWire_UI (Package)": {
"commandName": "MsixPackage"
},
"BeetleWire_UI (Unpackaged)": {
"commandName": "Project"
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Net;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
namespace BeetleWire_UI.Services;
public class ConnectionManager
{
private static ConnectionManager _instance;
public static ConnectionManager Instance => _instance ??= new ConnectionManager();
public HttpListener ServerListener { get; private set; }
public ClientWebSocket ClientSocket { get; private set; }
private ConnectionManager() { }
public void StartServer(string prefix)
{
if (ServerListener == null)
{
ServerListener = new HttpListener();
ServerListener.Prefixes.Add(prefix);
ServerListener.Start();
}
}
public void StopServer()
{
ServerListener?.Stop();
ServerListener = null;
}
public async Task ConnectClientAsync(Uri uri, CancellationToken token)
{
if (ClientSocket == null)
{
ClientSocket = new ClientWebSocket();
await ClientSocket.ConnectAsync(uri, token);
}
}
public void DisconnectClient()
{
ClientSocket?.Dispose();
ClientSocket = null;
}
}

19
app.manifest Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="BeetleWire_UI.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
</assembly>