mirror of
https://github.com/scratchfoundation/scratch-link.git
synced 2024-11-14 19:05:03 -05:00
make ScratchLinkApp to take the place of MAUI app
Note that this builds and runs, but doesn't work: the Xamarin.Mac implementation of `System.Net` always sets `IsWebSocketRequest` to `false` and doesn't actually support WebSockets connections as a server.
This commit is contained in:
parent
2c9e4c600a
commit
8be1d8b021
7 changed files with 166 additions and 99 deletions
|
@ -11,6 +11,7 @@ using System.Net.WebSockets;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using ScratchLink.Extensions;
|
using ScratchLink.Extensions;
|
||||||
using ScratchLink.JsonRpc;
|
using ScratchLink.JsonRpc;
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ internal abstract class BLESession<TUUID> : Session
|
||||||
public BLESession(WebSocketContext context)
|
public BLESession(WebSocketContext context)
|
||||||
: base(context)
|
: base(context)
|
||||||
{
|
{
|
||||||
this.GattHelpers = IPlatformApplication.Current.Services.GetService<GattHelpers<TUUID>>();
|
this.GattHelpers = ScratchLinkApp.Current.Services.GetService<GattHelpers<TUUID>>();
|
||||||
this.AllowedServices = new ();
|
this.AllowedServices = new ();
|
||||||
this.Handlers["discover"] = this.HandleDiscover;
|
this.Handlers["discover"] = this.HandleDiscover;
|
||||||
this.Handlers["connect"] = this.HandleConnect;
|
this.Handlers["connect"] = this.HandleConnect;
|
||||||
|
|
150
scratch-link-common/ScratchLinkApp.cs
Normal file
150
scratch-link-common/ScratchLinkApp.cs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
// <copyright file="ScratchLinkApp.cs" company="Scratch Foundation">
|
||||||
|
// Copyright (c) Scratch Foundation. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
namespace ScratchLink;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using ScratchLink.BLE;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Main entry point for Scratch Link and central service provider for dependency injection.
|
||||||
|
/// </summary>
|
||||||
|
public class ScratchLinkApp
|
||||||
|
{
|
||||||
|
private const int WebSocketPort = 20111;
|
||||||
|
|
||||||
|
private readonly SessionManager sessionManager;
|
||||||
|
private readonly WebSocketListener webSocketListener;
|
||||||
|
|
||||||
|
private ScratchLinkApp(IServiceProvider platformServicesProvider)
|
||||||
|
{
|
||||||
|
this.Services = platformServicesProvider;
|
||||||
|
if (Current != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Attempt to create a second app instance");
|
||||||
|
}
|
||||||
|
|
||||||
|
Current = this;
|
||||||
|
|
||||||
|
this.sessionManager = this.Services.GetService<SessionManager>();
|
||||||
|
this.webSocketListener = new ()
|
||||||
|
{
|
||||||
|
OnWebSocketConnection = (webSocketContext) =>
|
||||||
|
{
|
||||||
|
this.sessionManager.ClientDidConnect(webSocketContext);
|
||||||
|
},
|
||||||
|
OnOtherConnection = (context) =>
|
||||||
|
{
|
||||||
|
context.Response.Headers.Clear();
|
||||||
|
context.Response.SendChunked = false;
|
||||||
|
context.Response.StatusCode = 426; // Upgrade Required
|
||||||
|
context.Response.OutputStream.Write(Encoding.UTF8.GetBytes("WebSockets required"));
|
||||||
|
context.Response.OutputStream.Close();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current app instance.
|
||||||
|
/// </summary>
|
||||||
|
public static ScratchLinkApp Current { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the platform-specific services provider.
|
||||||
|
/// This provides access to services like the session manager or GATT helpers.
|
||||||
|
/// </summary>
|
||||||
|
public IServiceProvider Services { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run the app.
|
||||||
|
/// </summary>
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
if (!HttpListener.IsSupported)
|
||||||
|
{
|
||||||
|
// TODO: pop up an error message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.webSocketListener.Start(new[]
|
||||||
|
{
|
||||||
|
string.Format("http://127.0.0.1:{0}/", WebSocketPort),
|
||||||
|
string.Format("http://localhost:{0}/", WebSocketPort),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleSessionDebug(WebSocketContext context)
|
||||||
|
{
|
||||||
|
var origin = context.Headers.Get("origin");
|
||||||
|
var socket = context.WebSocket;
|
||||||
|
Debug.Print("New connection");
|
||||||
|
socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a Scratch Link app instance.
|
||||||
|
/// Fills the role of the .NET generic host or <c>MauiAppBuilder</c>.
|
||||||
|
/// </summary>
|
||||||
|
public class Builder
|
||||||
|
{
|
||||||
|
private string[] arguments;
|
||||||
|
private Type sessionManagerType;
|
||||||
|
private Type gattHelpersBaseType;
|
||||||
|
private Type gattHelpersType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the arguments which will be passed to the app host.
|
||||||
|
/// Must be called before Build().
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="arguments">Command line arguments from app invocation.</param>
|
||||||
|
public void SetArguments(string[] arguments)
|
||||||
|
{
|
||||||
|
this.arguments = arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the type which will be used to build the platform-specific session manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TSessionManager">The platform-specific session manager type.</typeparam>
|
||||||
|
internal void SetSessionManager<TSessionManager>()
|
||||||
|
where TSessionManager : SessionManager
|
||||||
|
{
|
||||||
|
this.sessionManagerType = typeof(TSessionManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the types which will be used to build the BLE GATT helpers.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TGattHelpers">The platform-specific GATT helpers type.</typeparam>
|
||||||
|
/// <typeparam name="TUUID">The platform-specific type for BLE UUID values.</typeparam>
|
||||||
|
internal void SetGattHelpers<TGattHelpers, TUUID>()
|
||||||
|
where TGattHelpers : GattHelpers<TUUID>
|
||||||
|
{
|
||||||
|
this.gattHelpersBaseType = typeof(GattHelpers<TUUID>);
|
||||||
|
this.gattHelpersType = typeof(TGattHelpers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a Scratch Link app host.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new Scratch Link app host.</returns>
|
||||||
|
internal ScratchLinkApp Build()
|
||||||
|
{
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
var serviceProviderOptions = new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true };
|
||||||
|
var servicesProvider = new DefaultServiceProviderFactory(serviceProviderOptions)
|
||||||
|
.CreateBuilder(serviceCollection)
|
||||||
|
.AddSingleton(typeof(SessionManager), this.sessionManagerType)
|
||||||
|
.AddSingleton(this.gattHelpersBaseType, this.gattHelpersType)
|
||||||
|
.BuildServiceProvider();
|
||||||
|
return new ScratchLinkApp(servicesProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,5 +27,6 @@
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Session.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Session.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)SessionManager.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)SessionManager.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)WebSocketListener.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)WebSocketListener.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)ScratchLinkApp.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
namespace ScratchLink.Mac;
|
namespace ScratchLink.Mac;
|
||||||
|
|
||||||
using AppKit;
|
using AppKit;
|
||||||
|
using CoreBluetooth;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
|
using ScratchLink.Mac.BLE;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Scratch Link's implementation of the NSApplicationDelegate protocol.
|
/// Scratch Link's implementation of the NSApplicationDelegate protocol.
|
||||||
|
@ -19,7 +21,14 @@ public class AppDelegate : NSApplicationDelegate
|
||||||
/// <param name="notification">A notification named <c>didFinishLaunchingNotification</c>.</param>
|
/// <param name="notification">A notification named <c>didFinishLaunchingNotification</c>.</param>
|
||||||
public override void DidFinishLaunching(NSNotification notification)
|
public override void DidFinishLaunching(NSNotification notification)
|
||||||
{
|
{
|
||||||
// Insert code here to initialize your application
|
var appBuilder = new ScratchLinkApp.Builder();
|
||||||
|
appBuilder.SetArguments(new NSProcessInfo().Arguments);
|
||||||
|
appBuilder.SetSessionManager<MacSessionManager>();
|
||||||
|
appBuilder.SetGattHelpers<MacGattHelpers, CBUUID>();
|
||||||
|
|
||||||
|
var app = appBuilder.Build();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -105,6 +105,9 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<AdditionalFiles Include="$(SolutionDir)stylecop.json" />
|
<AdditionalFiles Include="$(SolutionDir)stylecop.json" />
|
||||||
|
<PackageReference Include="Fleck">
|
||||||
|
<Version>1.2.0</Version>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Text.Json">
|
<PackageReference Include="System.Text.Json">
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
|
||||||
xmlns:local="clr-namespace:ScratchLink"
|
|
||||||
x:Class="ScratchLink.ScratchLinkApp">
|
|
||||||
<Application.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
|
|
||||||
<Color x:Key="PrimaryColor">#512bdf</Color>
|
|
||||||
<Color x:Key="SecondaryColor">White</Color>
|
|
||||||
|
|
||||||
<Style TargetType="Label">
|
|
||||||
<Setter Property="TextColor" Value="{DynamicResource PrimaryColor}" />
|
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style TargetType="Button">
|
|
||||||
<Setter Property="TextColor" Value="{DynamicResource SecondaryColor}" />
|
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
|
||||||
<Setter Property="BackgroundColor" Value="{DynamicResource PrimaryColor}" />
|
|
||||||
<Setter Property="Padding" Value="14,10" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
</ResourceDictionary>
|
|
||||||
</Application.Resources>
|
|
||||||
</Application>
|
|
|
@ -1,72 +0,0 @@
|
||||||
// <copyright file="ScratchLinkApp.xaml.cs" company="Scratch Foundation">
|
|
||||||
// Copyright (c) Scratch Foundation. All rights reserved.
|
|
||||||
// </copyright>
|
|
||||||
|
|
||||||
namespace ScratchLink;
|
|
||||||
|
|
||||||
using ScratchLink.Resources.Strings;
|
|
||||||
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="ScratchLinkApp"/> class contains the cross-platform entry point for the application.
|
|
||||||
/// </summary>
|
|
||||||
public partial class ScratchLinkApp : Application
|
|
||||||
{
|
|
||||||
private const int WebSocketPort = 20111;
|
|
||||||
|
|
||||||
private readonly SessionManager sessionManager;
|
|
||||||
private readonly WebSocketListener webSocketListener;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ScratchLinkApp"/> class.
|
|
||||||
/// This is the cross-platform entry point.
|
|
||||||
/// </summary>
|
|
||||||
public ScratchLinkApp()
|
|
||||||
{
|
|
||||||
this.InitializeComponent();
|
|
||||||
|
|
||||||
this.MainPage = new MainPage();
|
|
||||||
|
|
||||||
if (!HttpListener.IsSupported)
|
|
||||||
{
|
|
||||||
// TODO: this doesn't work right
|
|
||||||
this.MainPage.DisplayAlert("Error", AppResource.HTTP_Listener_not_supported, "Quit").ContinueWith(task =>
|
|
||||||
{
|
|
||||||
this.Quit();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sessionManager = IPlatformApplication.Current.Services.GetService<SessionManager>();
|
|
||||||
|
|
||||||
this.webSocketListener = new ()
|
|
||||||
{
|
|
||||||
OnWebSocketConnection = (webSocketContext) =>
|
|
||||||
{
|
|
||||||
this.sessionManager.ClientDidConnect(webSocketContext);
|
|
||||||
},
|
|
||||||
OnOtherConnection = (context) =>
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
this.webSocketListener.Start(new[]
|
|
||||||
{
|
|
||||||
string.Format("http://127.0.0.1:{0}/", WebSocketPort),
|
|
||||||
string.Format("http://localhost:{0}/", WebSocketPort),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleSessionDebug(WebSocketContext context)
|
|
||||||
{
|
|
||||||
var origin = context.Headers.Get("origin");
|
|
||||||
var socket = context.WebSocket;
|
|
||||||
this.Dispatcher.Dispatch(() =>
|
|
||||||
{
|
|
||||||
this.MainPage.DisplayAlert("New connection", string.Format("Path: {0}\nOrigin: {1}", context.RequestUri.AbsolutePath, origin), "OK");
|
|
||||||
});
|
|
||||||
socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue