mirror of
https://github.com/jwetzell/qController.git
synced 2026-08-20 14:39:12 +00:00
Generated
+45107
-45108
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version = "1.0" encoding = "UTF-8" ?>
|
||||||
|
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:local="clr-namespace:qControlMAUI"
|
||||||
|
x:Class="qControlMAUI.App">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
||||||
|
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public App()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
MainPage = new AppShell();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<Shell
|
||||||
|
x:Class="qControlMAUI.AppShell"
|
||||||
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:local="clr-namespace:qControlMAUI"
|
||||||
|
Shell.FlyoutBehavior="Disabled"
|
||||||
|
Title="qControlMAUI">
|
||||||
|
|
||||||
|
<ShellContent
|
||||||
|
Title="Home"
|
||||||
|
ContentTemplate="{DataTemplate local:MainPage}"
|
||||||
|
Route="MainPage" />
|
||||||
|
|
||||||
|
</Shell>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
public partial class AppShell : Shell
|
||||||
|
{
|
||||||
|
public AppShell()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="qControlMAUI.MainPage">
|
||||||
|
|
||||||
|
<ScrollView>
|
||||||
|
<VerticalStackLayout
|
||||||
|
Padding="30,0"
|
||||||
|
Spacing="25">
|
||||||
|
<Image
|
||||||
|
Source="dotnet_bot.png"
|
||||||
|
HeightRequest="185"
|
||||||
|
Aspect="AspectFit"
|
||||||
|
SemanticProperties.Description="dot net bot in a race car number eight" />
|
||||||
|
|
||||||
|
<Label
|
||||||
|
Text="Hello, World!"
|
||||||
|
Style="{StaticResource Headline}"
|
||||||
|
SemanticProperties.HeadingLevel="Level1" />
|
||||||
|
|
||||||
|
<Label
|
||||||
|
Text="Welcome to .NET Multi-platform App UI"
|
||||||
|
Style="{StaticResource SubHeadline}"
|
||||||
|
SemanticProperties.HeadingLevel="Level2"
|
||||||
|
SemanticProperties.Description="Welcome to dot net Multi platform App U I" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
x:Name="CounterBtn"
|
||||||
|
Text="Click me"
|
||||||
|
SemanticProperties.Hint="Counts the number of times you click"
|
||||||
|
Clicked="OnCounterClicked"
|
||||||
|
HorizontalOptions="Fill" />
|
||||||
|
</VerticalStackLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</ContentPage>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
public partial class MainPage : ContentPage
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
public MainPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCounterClicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count == 1)
|
||||||
|
CounterBtn.Text = $"Clicked {count} time";
|
||||||
|
else
|
||||||
|
CounterBtn.Text = $"Clicked {count} times";
|
||||||
|
|
||||||
|
SemanticScreenReader.Announce(CounterBtn.Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
public static class MauiProgram
|
||||||
|
{
|
||||||
|
public static MauiApp CreateMauiApp()
|
||||||
|
{
|
||||||
|
var builder = MauiApp.CreateBuilder();
|
||||||
|
builder
|
||||||
|
.UseMauiApp<App>()
|
||||||
|
.ConfigureFonts(fonts =>
|
||||||
|
{
|
||||||
|
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||||
|
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||||
|
});
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
builder.Logging.AddDebug();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Android.App;
|
||||||
|
using Android.Content.PM;
|
||||||
|
using Android.OS;
|
||||||
|
|
||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||||
|
public class MainActivity : MauiAppCompatActivity
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Android.App;
|
||||||
|
using Android.Runtime;
|
||||||
|
|
||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
[Application]
|
||||||
|
public class MainApplication : MauiApplication
|
||||||
|
{
|
||||||
|
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||||
|
: base(handle, ownership)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#512BD4</color>
|
||||||
|
<color name="colorPrimaryDark">#2B0B98</color>
|
||||||
|
<color name="colorAccent">#2B0B98</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Foundation;
|
||||||
|
|
||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
[Register("AppDelegate")]
|
||||||
|
public class AppDelegate : MauiUIApplicationDelegate
|
||||||
|
{
|
||||||
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<!-- See https://aka.ms/maui-publish-app-store#add-entitlements for more information about adding entitlements.-->
|
||||||
|
<dict>
|
||||||
|
<!-- App Sandbox must be enabled to distribute a MacCatalyst app through the Mac App Store. -->
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<!-- When App Sandbox is enabled, this value is required to open outgoing network connections. -->
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<!-- The Mac App Store requires you specify if the app uses encryption. -->
|
||||||
|
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
|
||||||
|
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
|
||||||
|
<!-- Please indicate <true/> or <false/> here. -->
|
||||||
|
|
||||||
|
<!-- Specify the category for your app here. -->
|
||||||
|
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
|
||||||
|
<!-- <key>LSApplicationCategoryType</key> -->
|
||||||
|
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
|
||||||
|
<key>UIDeviceFamily</key>
|
||||||
|
<array>
|
||||||
|
<integer>2</integer>
|
||||||
|
</array>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>XSAppIconAssets</key>
|
||||||
|
<string>Assets.xcassets/appicon.appiconset</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using ObjCRuntime;
|
||||||
|
using UIKit;
|
||||||
|
|
||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
// This is the main entry point of the application.
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||||
|
// you can specify it here.
|
||||||
|
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Microsoft.Maui;
|
||||||
|
using Microsoft.Maui.Hosting;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
internal class Program : MauiApplication
|
||||||
|
{
|
||||||
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var app = new Program();
|
||||||
|
app.Run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="8" xmlns="http://tizen.org/ns/packages">
|
||||||
|
<profile name="common" />
|
||||||
|
<ui-application appid="maui-application-id-placeholder" exec="qControlMAUI.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
|
||||||
|
<label>maui-application-title-placeholder</label>
|
||||||
|
<icon>maui-appicon-placeholder</icon>
|
||||||
|
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
|
||||||
|
</ui-application>
|
||||||
|
<shortcut-list />
|
||||||
|
<privileges>
|
||||||
|
<privilege>http://tizen.org/privilege/internet</privilege>
|
||||||
|
</privileges>
|
||||||
|
<dependencies />
|
||||||
|
<provides-appdefined-privileges />
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<maui:MauiWinUIApplication
|
||||||
|
x:Class="qControlMAUI.WinUI.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:maui="using:Microsoft.Maui"
|
||||||
|
xmlns:local="using:qControlMAUI.WinUI">
|
||||||
|
|
||||||
|
</maui:MauiWinUIApplication>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
|
namespace qControlMAUI.WinUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides application-specific behavior to supplement the default Application class.
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : MauiWinUIApplication
|
||||||
|
{
|
||||||
|
/// <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();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Package
|
||||||
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
|
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||||
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||||
|
IgnorableNamespaces="uap rescap">
|
||||||
|
|
||||||
|
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
|
||||||
|
|
||||||
|
<mp:PhoneIdentity PhoneProductId="B9BD1003-B46F-4997-B80F-501D62A4D047" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||||
|
|
||||||
|
<Properties>
|
||||||
|
<DisplayName>$placeholder$</DisplayName>
|
||||||
|
<PublisherDisplayName>User Name</PublisherDisplayName>
|
||||||
|
<Logo>$placeholder$.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="$placeholder$"
|
||||||
|
Description="$placeholder$"
|
||||||
|
Square150x150Logo="$placeholder$.png"
|
||||||
|
Square44x44Logo="$placeholder$.png"
|
||||||
|
BackgroundColor="transparent">
|
||||||
|
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
|
||||||
|
<uap:SplashScreen Image="$placeholder$.png" />
|
||||||
|
</uap:VisualElements>
|
||||||
|
</Application>
|
||||||
|
</Applications>
|
||||||
|
|
||||||
|
<Capabilities>
|
||||||
|
<rescap:Capability Name="runFullTrust" />
|
||||||
|
</Capabilities>
|
||||||
|
|
||||||
|
</Package>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?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="qControlMAUI.WinUI.app"/>
|
||||||
|
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<!-- The combination of below two tags have the following effect:
|
||||||
|
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||||
|
2) System < Windows 10 Anniversary Update
|
||||||
|
-->
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Foundation;
|
||||||
|
|
||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
[Register("AppDelegate")]
|
||||||
|
public class AppDelegate : MauiUIApplicationDelegate
|
||||||
|
{
|
||||||
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIDeviceFamily</key>
|
||||||
|
<array>
|
||||||
|
<integer>1</integer>
|
||||||
|
<integer>2</integer>
|
||||||
|
</array>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>XSAppIconAssets</key>
|
||||||
|
<string>Assets.xcassets/appicon.appiconset</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using ObjCRuntime;
|
||||||
|
using UIKit;
|
||||||
|
|
||||||
|
namespace qControlMAUI
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
// This is the main entry point of the application.
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||||
|
// you can specify it here.
|
||||||
|
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
This is the minimum required version of the Apple Privacy Manifest for .NET MAUI apps.
|
||||||
|
The contents below are needed because of APIs that are used in the .NET framework and .NET MAUI SDK.
|
||||||
|
|
||||||
|
You are responsible for adding extra entries as needed for your application.
|
||||||
|
|
||||||
|
More information: https://aka.ms/maui-privacy-manifest
|
||||||
|
-->
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>C617.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>35F9.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>E174.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<!--
|
||||||
|
The entry below is only needed when you're using the Preferences API in your app.
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>CA92.1</string>
|
||||||
|
</array>
|
||||||
|
</dict> -->
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Windows Machine": {
|
||||||
|
"commandName": "MsixPackage",
|
||||||
|
"nativeDebugging": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 231 B |
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
@@ -0,0 +1,15 @@
|
|||||||
|
Any raw assets you want to be deployed with your application can be placed in
|
||||||
|
this directory (and child directories). Deployment of the asset to your application
|
||||||
|
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
|
||||||
|
|
||||||
|
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||||
|
|
||||||
|
These files will be deployed with your package and will be accessible using Essentials:
|
||||||
|
|
||||||
|
async Task LoadMauiAsset()
|
||||||
|
{
|
||||||
|
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
|
||||||
|
using var reader = new StreamReader(stream);
|
||||||
|
|
||||||
|
var contents = reader.ReadToEnd();
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<?xaml-comp compile="true" ?>
|
||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||||
|
|
||||||
|
<!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
|
||||||
|
|
||||||
|
<Color x:Key="Primary">#512BD4</Color>
|
||||||
|
<Color x:Key="PrimaryDark">#ac99ea</Color>
|
||||||
|
<Color x:Key="PrimaryDarkText">#242424</Color>
|
||||||
|
<Color x:Key="Secondary">#DFD8F7</Color>
|
||||||
|
<Color x:Key="SecondaryDarkText">#9880e5</Color>
|
||||||
|
<Color x:Key="Tertiary">#2B0B98</Color>
|
||||||
|
|
||||||
|
<Color x:Key="White">White</Color>
|
||||||
|
<Color x:Key="Black">Black</Color>
|
||||||
|
<Color x:Key="Magenta">#D600AA</Color>
|
||||||
|
<Color x:Key="MidnightBlue">#190649</Color>
|
||||||
|
<Color x:Key="OffBlack">#1f1f1f</Color>
|
||||||
|
|
||||||
|
<Color x:Key="Gray100">#E1E1E1</Color>
|
||||||
|
<Color x:Key="Gray200">#C8C8C8</Color>
|
||||||
|
<Color x:Key="Gray300">#ACACAC</Color>
|
||||||
|
<Color x:Key="Gray400">#919191</Color>
|
||||||
|
<Color x:Key="Gray500">#6E6E6E</Color>
|
||||||
|
<Color x:Key="Gray600">#404040</Color>
|
||||||
|
<Color x:Key="Gray900">#212121</Color>
|
||||||
|
<Color x:Key="Gray950">#141414</Color>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
|
||||||
|
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
|
||||||
|
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
|
||||||
|
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
|
||||||
|
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
|
||||||
|
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,427 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<?xaml-comp compile="true" ?>
|
||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||||
|
|
||||||
|
<Style TargetType="ActivityIndicator">
|
||||||
|
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="IndicatorView">
|
||||||
|
<Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
|
||||||
|
<Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
<Setter Property="StrokeShape" Value="Rectangle"/>
|
||||||
|
<Setter Property="StrokeThickness" Value="1"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="BoxView">
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource PrimaryDarkText}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="BorderWidth" Value="0"/>
|
||||||
|
<Setter Property="CornerRadius" Value="8"/>
|
||||||
|
<Setter Property="Padding" Value="14,10"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="PointerOver" />
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="CheckBox">
|
||||||
|
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="DatePicker">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Editor">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Entry">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Frame">
|
||||||
|
<Setter Property="HasShadow" Value="False" />
|
||||||
|
<Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||||
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ImageButton">
|
||||||
|
<Setter Property="Opacity" Value="1" />
|
||||||
|
<Setter Property="BorderColor" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderWidth" Value="0"/>
|
||||||
|
<Setter Property="CornerRadius" Value="0"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="PointerOver" />
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Label">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Span">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Label" x:Key="Headline">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="FontSize" Value="32" />
|
||||||
|
<Setter Property="HorizontalOptions" Value="Center" />
|
||||||
|
<Setter Property="HorizontalTextAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Label" x:Key="SubHeadline">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="FontSize" Value="24" />
|
||||||
|
<Setter Property="HorizontalOptions" Value="Center" />
|
||||||
|
<Setter Property="HorizontalTextAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ListView">
|
||||||
|
<Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
<Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Picker">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ProgressBar">
|
||||||
|
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="RadioButton">
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent"/>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="RefreshView">
|
||||||
|
<Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="SearchBar">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||||
|
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="SearchHandler">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Shadow">
|
||||||
|
<Setter Property="Radius" Value="15" />
|
||||||
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
|
<Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="Offset" Value="10,10" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Slider">
|
||||||
|
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||||
|
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||||
|
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="SwipeItem">
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Switch">
|
||||||
|
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="ThumbColor" Value="{StaticResource White}" />
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="On">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="Off">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TimePicker">
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="BackgroundColor" Value="Transparent"/>
|
||||||
|
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44"/>
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44"/>
|
||||||
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
|
<VisualStateGroupList>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal" />
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateGroupList>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Page" ApplyToDerivedTypes="True">
|
||||||
|
<Setter Property="Padding" Value="0"/>
|
||||||
|
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Shell" ApplyToDerivedTypes="True">
|
||||||
|
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
||||||
|
<Setter Property="Shell.ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
|
||||||
|
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
|
||||||
|
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||||
|
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="Shell.NavBarHasShadow" Value="False" />
|
||||||
|
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||||
|
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="NavigationPage">
|
||||||
|
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
||||||
|
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TabbedPage">
|
||||||
|
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
|
||||||
|
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||||
|
<Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
|
||||||
|
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
|
||||||
|
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||||
|
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
||||||
|
|
||||||
|
<!-- Note for MacCatalyst:
|
||||||
|
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
|
||||||
|
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
|
||||||
|
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
|
||||||
|
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
|
||||||
|
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->
|
||||||
|
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>qControlMAUI</RootNamespace>
|
||||||
|
<UseMaui>true</UseMaui>
|
||||||
|
<SingleProject>true</SingleProject>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<!-- Display name -->
|
||||||
|
<ApplicationTitle>qControlMAUI</ApplicationTitle>
|
||||||
|
|
||||||
|
<!-- App Identifier -->
|
||||||
|
<ApplicationId>com.companyname.qcontrolmaui</ApplicationId>
|
||||||
|
|
||||||
|
<!-- Versions -->
|
||||||
|
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||||
|
<ApplicationVersion>1</ApplicationVersion>
|
||||||
|
|
||||||
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
|
||||||
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion>
|
||||||
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
|
||||||
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||||
|
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- App Icon -->
|
||||||
|
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||||
|
|
||||||
|
<!-- Splash Screen -->
|
||||||
|
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
|
||||||
|
|
||||||
|
<!-- Images -->
|
||||||
|
<MauiImage Include="Resources\Images\*" />
|
||||||
|
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
|
||||||
|
|
||||||
|
<!-- Custom Fonts -->
|
||||||
|
<MauiFont Include="Resources\Fonts\*" />
|
||||||
|
|
||||||
|
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||||
|
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+29
-2
@@ -1,6 +1,6 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 16
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 16.0.29424.173
|
VisualStudioVersion = 17.12.35707.178
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qController.iOS", "iOS\qController.iOS.csproj", "{A30F10F2-4CBC-47E6-841D-8DCAD05BAA0D}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qController.iOS", "iOS\qController.iOS.csproj", "{A30F10F2-4CBC-47E6-841D-8DCAD05BAA0D}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -8,6 +8,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qController.Droid", "Droid\
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "qController", "qController.Standard\qController.csproj", "{86160AAC-2150-41B0-BB06-4442CA054A78}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "qController", "qController.Standard\qController.csproj", "{86160AAC-2150-41B0-BB06-4442CA054A78}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Xamarin.Forms", "Xamarin.Forms", "{862BB23A-99C3-4550-B2DD-AFB554060BB0}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qController", "qController\qController.csproj", "{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -56,10 +60,33 @@ Global
|
|||||||
{86160AAC-2150-41B0-BB06-4442CA054A78}.Release|iPhone.Build.0 = Release|Any CPU
|
{86160AAC-2150-41B0-BB06-4442CA054A78}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
{86160AAC-2150-41B0-BB06-4442CA054A78}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{86160AAC-2150-41B0-BB06-4442CA054A78}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{86160AAC-2150-41B0-BB06-4442CA054A78}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{86160AAC-2150-41B0-BB06-4442CA054A78}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Debug|iPhone.Deploy.0 = Debug|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Release|iPhone.Deploy.0 = Release|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{63B39EEA-36D4-4B2C-8FFD-FB0E12FD8B14}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{A30F10F2-4CBC-47E6-841D-8DCAD05BAA0D} = {862BB23A-99C3-4550-B2DD-AFB554060BB0}
|
||||||
|
{D960ED89-25D2-46E8-B24C-1FFFCD19C0D9} = {862BB23A-99C3-4550-B2DD-AFB554060BB0}
|
||||||
|
{86160AAC-2150-41B0-BB06-4442CA054A78} = {862BB23A-99C3-4550-B2DD-AFB554060BB0}
|
||||||
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {3DBBFC12-E39D-4331-B179-FF5D0493949C}
|
SolutionGuid = {3DBBFC12-E39D-4331-B179-FF5D0493949C}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<Shell
|
||||||
|
x:Class="qController.AppShell"
|
||||||
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:local="clr-namespace:qController"
|
||||||
|
Shell.FlyoutBehavior="Disabled"
|
||||||
|
Title="qController">
|
||||||
|
|
||||||
|
<ShellContent
|
||||||
|
Title="Home"
|
||||||
|
ContentTemplate="{DataTemplate local:RootPage}"
|
||||||
|
Route="MainPage" />
|
||||||
|
|
||||||
|
</Shell>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace qController
|
||||||
|
{
|
||||||
|
public partial class AppShell : Shell
|
||||||
|
{
|
||||||
|
public AppShell()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
namespace qController
|
||||||
|
{
|
||||||
|
public class QButton : Button
|
||||||
|
{
|
||||||
|
public QCommand qCommand
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QButton(QCommand command)
|
||||||
|
{
|
||||||
|
|
||||||
|
qCommand = command;
|
||||||
|
Text = qCommand.text;
|
||||||
|
TextColor = Colors.Black;
|
||||||
|
FontSize = App.WidthUnit * 5;
|
||||||
|
if (qCommand.osc.Contains("go"))
|
||||||
|
{
|
||||||
|
BackgroundColor = Colors.SeaGreen;
|
||||||
|
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||||
|
switch (Device.RuntimePlatform)
|
||||||
|
{
|
||||||
|
case Device.iOS:
|
||||||
|
FontSize = App.HeightUnit * 4;
|
||||||
|
FontAttributes = FontAttributes.Bold;
|
||||||
|
break;
|
||||||
|
case Device.Android:
|
||||||
|
FontSize = App.HeightUnit * 4;
|
||||||
|
FontAttributes = FontAttributes.Bold;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (qCommand.osc.Contains("panic"))
|
||||||
|
{
|
||||||
|
BackgroundColor = Colors.IndianRed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromArgb("D8D8D8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
namespace qController
|
||||||
|
{
|
||||||
|
public class QLevelsButton : ShadowButton
|
||||||
|
{
|
||||||
|
public Button button;
|
||||||
|
public QLevelsButton()
|
||||||
|
{
|
||||||
|
button = new Button
|
||||||
|
{
|
||||||
|
Text = QIcon.SLIDERS,
|
||||||
|
TextColor = Colors.Black,
|
||||||
|
FontFamily = App.QFont,
|
||||||
|
FontSize = App.HeightUnit * 4,
|
||||||
|
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||||
|
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||||
|
HeightRequest = App.HeightUnit * 8,
|
||||||
|
WidthRequest = App.HeightUnit * 8,
|
||||||
|
CornerRadius = (int)(App.HeightUnit * 4),
|
||||||
|
BackgroundColor = Colors.LightBlue
|
||||||
|
};
|
||||||
|
|
||||||
|
HeightRequest = button.Height;
|
||||||
|
WidthRequest = button.Width;
|
||||||
|
CornerRadius = button.CornerRadius;
|
||||||
|
Content = button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
namespace qController
|
||||||
|
{
|
||||||
|
public class ShadowButton : Frame
|
||||||
|
{
|
||||||
|
public ShadowButton(Button b)
|
||||||
|
{
|
||||||
|
//Content = b;
|
||||||
|
HeightRequest = b.Height;
|
||||||
|
WidthRequest = b.Width;
|
||||||
|
CornerRadius = b.CornerRadius;
|
||||||
|
Padding = new Thickness(0);
|
||||||
|
Content = b;
|
||||||
|
HasShadow = true;
|
||||||
|
BorderColor = Colors.Black;
|
||||||
|
IsVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShadowButton()
|
||||||
|
{
|
||||||
|
HasShadow = true;
|
||||||
|
BorderColor = Colors.Black;
|
||||||
|
IsVisible = false;
|
||||||
|
Padding = new Thickness(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
|
||||||
|
namespace qController.Cell
|
||||||
|
{
|
||||||
|
public class QControlsBlock : Frame
|
||||||
|
{
|
||||||
|
Grid? mainG;
|
||||||
|
|
||||||
|
EventHandler callback;
|
||||||
|
|
||||||
|
public QControlsBlock(EventHandler callback)
|
||||||
|
{
|
||||||
|
this.callback = callback;
|
||||||
|
|
||||||
|
Margin = new Thickness(10);
|
||||||
|
Padding = new Thickness(0);
|
||||||
|
IsVisible = true;
|
||||||
|
|
||||||
|
|
||||||
|
BackgroundColor = Colors.Transparent;
|
||||||
|
//highlight Button Grid
|
||||||
|
//BackgroundColor = Color.FromHex("FF0000");
|
||||||
|
|
||||||
|
List<QCommand> commands = new List<QCommand>();
|
||||||
|
commands.Add(QCommands.PREVIOUS);
|
||||||
|
commands.Add(QCommands.PAUSE);
|
||||||
|
commands.Add(QCommands.NEXT);
|
||||||
|
commands.Add(QCommands.PREVIEW);
|
||||||
|
commands.Add(QCommands.PANIC);
|
||||||
|
commands.Add(QCommands.RESUME);
|
||||||
|
//setCustomButtons(commands);
|
||||||
|
setDefaultButtons();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDefaultButtons()
|
||||||
|
{
|
||||||
|
List<QButton> buttons = new List<QButton>();
|
||||||
|
|
||||||
|
buttons.Add(new QButton(QCommands.PREVIOUS));
|
||||||
|
buttons.Add(new QButton(QCommands.PANIC));
|
||||||
|
buttons.Add(new QButton(QCommands.NEXT));
|
||||||
|
buttons.Add(new QButton(QCommands.PREVIEW));
|
||||||
|
buttons.Add(new QButton(QCommands.PAUSE));
|
||||||
|
buttons.Add(new QButton(QCommands.RESUME));
|
||||||
|
|
||||||
|
mainG = new Grid
|
||||||
|
{
|
||||||
|
Padding = new Thickness(0),
|
||||||
|
RowDefinitions = {
|
||||||
|
new RowDefinition{Height = GridLength.Star},
|
||||||
|
new RowDefinition{Height = GridLength.Star},
|
||||||
|
new RowDefinition{Height = GridLength.Star}
|
||||||
|
},
|
||||||
|
ColumnDefinitions = {
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)}
|
||||||
|
},
|
||||||
|
Margin = new Thickness(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
int row = 0;
|
||||||
|
int column = 0;
|
||||||
|
for (int i = 0; i < buttons.Count; i++)
|
||||||
|
{
|
||||||
|
QButton b = buttons[i];
|
||||||
|
b.Clicked += callback;
|
||||||
|
mainG.Add(b, column, row);
|
||||||
|
row++;
|
||||||
|
if (row == 3)
|
||||||
|
{
|
||||||
|
row = 0;
|
||||||
|
column = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QButton goButton = new QButton(QCommands.GO);
|
||||||
|
goButton.Clicked += callback;
|
||||||
|
mainG.Add(goButton, 1, 0);
|
||||||
|
Grid.SetRowSpan(goButton, 3);
|
||||||
|
Content = mainG;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomButtons(List<QCommand> commands)
|
||||||
|
{
|
||||||
|
mainG = new Grid
|
||||||
|
{
|
||||||
|
Padding = new Thickness(0),
|
||||||
|
RowDefinitions = {
|
||||||
|
new RowDefinition{Height = GridLength.Auto},
|
||||||
|
new RowDefinition{Height = GridLength.Auto},
|
||||||
|
new RowDefinition{Height = GridLength.Auto}
|
||||||
|
},
|
||||||
|
ColumnDefinitions = {
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)}
|
||||||
|
},
|
||||||
|
Margin = new Thickness(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
int row = 0;
|
||||||
|
int column = 0;
|
||||||
|
for (int i = 0; i < commands.Count; i++)
|
||||||
|
{
|
||||||
|
QButton b = new QButton(commands[i]);
|
||||||
|
b.Clicked += callback;
|
||||||
|
mainG.Add(b, column, row);
|
||||||
|
row++;
|
||||||
|
if (row == 3)
|
||||||
|
{
|
||||||
|
row = 0;
|
||||||
|
column = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QButton goButton = new QButton(QCommands.GO);
|
||||||
|
goButton.Clicked += callback;
|
||||||
|
mainG.Add(goButton, 1, 0);
|
||||||
|
Grid.SetRowSpan(goButton, 3);
|
||||||
|
Content = mainG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using qController.QItems;
|
||||||
|
|
||||||
|
namespace qController.Cell
|
||||||
|
{
|
||||||
|
public class QCueListCell : Frame
|
||||||
|
{
|
||||||
|
public ListView cueListView;
|
||||||
|
public Button closeButton;
|
||||||
|
public ObservableCollection<OSCListItem> items;
|
||||||
|
public QCueListCell(QCueList qCueList)
|
||||||
|
{
|
||||||
|
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||||
|
switch (Device.RuntimePlatform)
|
||||||
|
{
|
||||||
|
case Device.iOS:
|
||||||
|
Margin = new Thickness(10, 30, 10, 10);
|
||||||
|
break;
|
||||||
|
case Device.Android:
|
||||||
|
Margin = new Thickness(10, 10, 10, 10);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Padding = new Thickness(10);
|
||||||
|
items = new ObservableCollection<OSCListItem>();
|
||||||
|
|
||||||
|
cueListView = new ListView
|
||||||
|
{
|
||||||
|
ItemsSource = items,
|
||||||
|
RowHeight = (int)(App.HeightUnit * 6),
|
||||||
|
ItemTemplate = new DataTemplate(() =>
|
||||||
|
{
|
||||||
|
var grid = new Grid { Padding = new Thickness(5, 10) };
|
||||||
|
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(30) });
|
||||||
|
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
|
||||||
|
|
||||||
|
|
||||||
|
var icon = new Label();
|
||||||
|
|
||||||
|
icon.FontFamily = App.QFont;
|
||||||
|
icon.SetBinding(Label.TextProperty, "Icon");
|
||||||
|
icon.HorizontalTextAlignment = TextAlignment.Center;
|
||||||
|
icon.VerticalTextAlignment = TextAlignment.Center;
|
||||||
|
icon.FontSize = App.HeightUnit * 3;
|
||||||
|
var label = new Label { VerticalOptions = LayoutOptions.FillAndExpand };
|
||||||
|
label.SetBinding(Label.TextProperty, "Text");
|
||||||
|
if (label.Text == "Disconnect")
|
||||||
|
{
|
||||||
|
label.TextColor = Colors.DarkRed;
|
||||||
|
}
|
||||||
|
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
|
||||||
|
switch (Device.RuntimePlatform)
|
||||||
|
{
|
||||||
|
case Device.iOS:
|
||||||
|
label.FontSize = App.HeightUnit * 3;
|
||||||
|
break;
|
||||||
|
case Device.Android:
|
||||||
|
label.FontSize = App.HeightUnit * 2.2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
grid.Add(icon);
|
||||||
|
grid.Add(label, 1, 0);
|
||||||
|
return new ViewCell { View = grid };
|
||||||
|
|
||||||
|
})
|
||||||
|
};
|
||||||
|
StackLayout layout = new StackLayout();
|
||||||
|
closeButton = new Button
|
||||||
|
{
|
||||||
|
BackgroundColor = Colors.Gray,
|
||||||
|
Text = "Close",
|
||||||
|
TextColor = Colors.White
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.Children.Add(closeButton);
|
||||||
|
layout.Children.Add(cueListView);
|
||||||
|
Content = layout;
|
||||||
|
|
||||||
|
for(int j=0; j < qCueList.cues.Count; j++)
|
||||||
|
{
|
||||||
|
var cue = qCueList.cues[j];
|
||||||
|
AddSubCues(cue, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSubCues(QCue cue, int level)
|
||||||
|
{
|
||||||
|
var cueIcon = cue.getIconString();
|
||||||
|
var cueTitle = "";
|
||||||
|
for (int i = 0; i < level; i++)
|
||||||
|
{
|
||||||
|
cueTitle += " ";
|
||||||
|
}
|
||||||
|
if (cue.number != "")
|
||||||
|
{
|
||||||
|
cueTitle += cue.number + " - " + cue.listName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cueTitle += cue.listName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cue.cues != null)
|
||||||
|
{
|
||||||
|
items.Add(new OSCListItem
|
||||||
|
{
|
||||||
|
Text = cueTitle,
|
||||||
|
Icon = cueIcon,
|
||||||
|
Command = "/select_id/" + cue.uniqueID
|
||||||
|
});
|
||||||
|
|
||||||
|
//uncomment to load nested group cues
|
||||||
|
for (int i = 0; i < cue.cues.Count; i++)
|
||||||
|
{
|
||||||
|
var sub_cue = cue.cues[i];
|
||||||
|
AddSubCues(sub_cue, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
items.Add(new OSCListItem
|
||||||
|
{
|
||||||
|
Text = cueTitle,
|
||||||
|
Icon = cueIcon,
|
||||||
|
Command = "/select_id/" + cue.uniqueID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace qController.Cell
|
||||||
|
{
|
||||||
|
public class QInstanceCell : ViewCell
|
||||||
|
{
|
||||||
|
public QInstanceCell()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
Label nameLabel = new Label();
|
||||||
|
Label addressLabel = new Label();
|
||||||
|
|
||||||
|
Label connectLabel = new Label();
|
||||||
|
Label deleteLabel = new Label();
|
||||||
|
|
||||||
|
var connectTapGesture = new TapGestureRecognizer();
|
||||||
|
var deleteTapGesture = new TapGestureRecognizer();
|
||||||
|
|
||||||
|
connectTapGesture.Tapped += Connect;
|
||||||
|
connectLabel.GestureRecognizers.Add(connectTapGesture);
|
||||||
|
|
||||||
|
|
||||||
|
deleteTapGesture.Tapped += Delete;
|
||||||
|
deleteLabel.GestureRecognizers.Add(deleteTapGesture);
|
||||||
|
|
||||||
|
InitItems();
|
||||||
|
|
||||||
|
|
||||||
|
//SET BINDINGS
|
||||||
|
nameLabel.SetBinding(Label.TextProperty, new Binding("name"));
|
||||||
|
addressLabel.SetBinding(Label.TextProperty,new Binding("address"));
|
||||||
|
|
||||||
|
Grid mainG = new Grid
|
||||||
|
{
|
||||||
|
//Padding = new Thickness(10),
|
||||||
|
RowDefinitions = {
|
||||||
|
new RowDefinition{Height = GridLength.Auto},
|
||||||
|
new RowDefinition{Height = GridLength.Auto}
|
||||||
|
},
|
||||||
|
ColumnDefinitions = {
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(4,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mainG.Add(nameLabel, 1, 0);
|
||||||
|
mainG.Add(addressLabel,1,1);
|
||||||
|
|
||||||
|
mainG.Add(connectLabel,2,0);
|
||||||
|
Grid.SetRowSpan(connectLabel,2);
|
||||||
|
mainG.Add(deleteLabel, 0, 0);
|
||||||
|
Grid.SetRowSpan(deleteLabel,2);
|
||||||
|
|
||||||
|
Frame f = new Frame
|
||||||
|
{
|
||||||
|
Content = mainG,
|
||||||
|
BorderColor = Colors.Black,
|
||||||
|
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||||
|
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||||
|
Padding = 0,
|
||||||
|
Margin = new Thickness(10,10,10,10),
|
||||||
|
CornerRadius=20,
|
||||||
|
BackgroundColor = Color.FromArgb("D8D8D8")
|
||||||
|
};
|
||||||
|
View = f;
|
||||||
|
|
||||||
|
|
||||||
|
void InitItems()
|
||||||
|
{
|
||||||
|
|
||||||
|
nameLabel.HorizontalTextAlignment = TextAlignment.Center;
|
||||||
|
nameLabel.VerticalTextAlignment = TextAlignment.End;
|
||||||
|
nameLabel.FontAttributes = FontAttributes.Bold;
|
||||||
|
nameLabel.FontSize = App.HeightUnit * 3;
|
||||||
|
nameLabel.Margin = new Thickness(0, 20, 0, 0);
|
||||||
|
|
||||||
|
addressLabel.HorizontalTextAlignment = TextAlignment.Center;
|
||||||
|
addressLabel.VerticalTextAlignment = TextAlignment.Start;
|
||||||
|
addressLabel.FontSize = App.HeightUnit * 2.5;
|
||||||
|
addressLabel.Margin = new Thickness(0, 0, 0, 20);
|
||||||
|
|
||||||
|
connectLabel.HorizontalOptions = LayoutOptions.StartAndExpand;
|
||||||
|
connectLabel.VerticalOptions = LayoutOptions.CenterAndExpand;
|
||||||
|
connectLabel.Text = QIcon.WIFI;
|
||||||
|
connectLabel.FontSize = App.HeightUnit * 5;
|
||||||
|
connectLabel.TextColor = Colors.LimeGreen;
|
||||||
|
|
||||||
|
deleteLabel.HorizontalOptions = LayoutOptions.CenterAndExpand;
|
||||||
|
deleteLabel.VerticalOptions = LayoutOptions.CenterAndExpand;
|
||||||
|
deleteLabel.Text = QIcon.TRASH_EMPTY;
|
||||||
|
deleteLabel.FontSize = App.HeightUnit * 5;
|
||||||
|
deleteLabel.TextColor = Colors.Red;
|
||||||
|
|
||||||
|
connectLabel.FontFamily = App.QFont;
|
||||||
|
deleteLabel.FontFamily = App.QFont;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Delete(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
Log.Debug("QINSTANCECELL - Delete " + nameLabel.Text + "," + addressLabel.Text + " Pressed");
|
||||||
|
QStorage.RemoveInstance(nameLabel.Text,addressLabel.Text);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Connect(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
App.rootPage.Detail.Navigation.PushAsync(new ControlPage(nameLabel.Text, addressLabel.Text));
|
||||||
|
Log.Debug("QINSTANCECELL - Connect To " + nameLabel.Text + " Pressed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
namespace qController.Cell
|
||||||
|
{
|
||||||
|
public class QLevelsCell : Frame
|
||||||
|
{
|
||||||
|
|
||||||
|
public List<double> levels;
|
||||||
|
public string activeCue;
|
||||||
|
public Slider mainSlider;
|
||||||
|
public Slider leftSlider;
|
||||||
|
public Slider rightSlider;
|
||||||
|
public Label mainLabel;
|
||||||
|
public Label rightLabel;
|
||||||
|
public Label leftLabel;
|
||||||
|
public QLevelsCell()
|
||||||
|
{
|
||||||
|
Padding = new Thickness(5);
|
||||||
|
CornerRadius = 20;
|
||||||
|
BorderColor = Colors.Black;
|
||||||
|
HasShadow = true;
|
||||||
|
BackgroundColor = Color.FromArgb("D8D8D8");
|
||||||
|
IsVisible = false;
|
||||||
|
//highlight for testing
|
||||||
|
//BackgroundColor = Color.Red;
|
||||||
|
|
||||||
|
|
||||||
|
Grid mainG = new Grid
|
||||||
|
{
|
||||||
|
Padding = new Thickness(0),
|
||||||
|
RowDefinitions = {
|
||||||
|
new RowDefinition{Height = GridLength.Star},
|
||||||
|
new RowDefinition{Height = GridLength.Star},
|
||||||
|
new RowDefinition{Height = GridLength.Star}
|
||||||
|
},
|
||||||
|
ColumnDefinitions = {
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mainSlider = new Slider
|
||||||
|
{
|
||||||
|
Minimum = -60,
|
||||||
|
Maximum = 12
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
leftSlider = new Slider
|
||||||
|
{
|
||||||
|
Minimum = -60,
|
||||||
|
Maximum = 12
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
rightSlider = new Slider
|
||||||
|
{
|
||||||
|
Minimum = -60,
|
||||||
|
Maximum = 12
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
mainLabel = new Label
|
||||||
|
{
|
||||||
|
Text = "MAIN:",
|
||||||
|
VerticalTextAlignment = TextAlignment.Center,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center
|
||||||
|
};
|
||||||
|
|
||||||
|
leftLabel = new Label
|
||||||
|
{
|
||||||
|
Text = "1:",
|
||||||
|
VerticalTextAlignment = TextAlignment.Center,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center
|
||||||
|
};
|
||||||
|
|
||||||
|
rightLabel = new Label
|
||||||
|
{
|
||||||
|
Text = "2:",
|
||||||
|
VerticalTextAlignment = TextAlignment.Center,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center
|
||||||
|
};
|
||||||
|
|
||||||
|
mainG.Add(mainSlider, 1, 0);
|
||||||
|
mainG.Add(leftSlider, 1, 1);
|
||||||
|
mainG.Add(rightSlider, 1, 2);
|
||||||
|
|
||||||
|
Grid.SetColumnSpan(mainSlider, 4);
|
||||||
|
Grid.SetColumnSpan(leftSlider, 4);
|
||||||
|
Grid.SetColumnSpan(rightSlider, 4);
|
||||||
|
|
||||||
|
mainG.Add(mainLabel, 0, 0);
|
||||||
|
mainG.Add(leftLabel, 0, 1);
|
||||||
|
mainG.Add(rightLabel, 0, 2);
|
||||||
|
|
||||||
|
Content = mainG;
|
||||||
|
Margin = new Thickness(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateLevels(List<double> lin_levels)
|
||||||
|
{
|
||||||
|
levels = lin_levels;
|
||||||
|
if (mainSlider != null)
|
||||||
|
{
|
||||||
|
mainSlider.Value = levels[0];
|
||||||
|
}
|
||||||
|
if (leftSlider != null)
|
||||||
|
{
|
||||||
|
leftSlider.Value = levels[1];
|
||||||
|
}
|
||||||
|
if (rightSlider != null)
|
||||||
|
{
|
||||||
|
rightSlider.Value = levels[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
using Acr.UserDialogs;
|
||||||
|
using qController.QItems;
|
||||||
|
|
||||||
|
namespace qController.Cell
|
||||||
|
{
|
||||||
|
public class CueEditArgs : EventArgs
|
||||||
|
{
|
||||||
|
public string CueID
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
public string Property
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
public string NewValue
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class QSelectedCueCell : Frame
|
||||||
|
{
|
||||||
|
public delegate void SelectedCueEditedHandler(object source, CueEditArgs args);
|
||||||
|
public event SelectedCueEditedHandler SelectedCueEdited;
|
||||||
|
|
||||||
|
Label name;
|
||||||
|
Label number;
|
||||||
|
Label type;
|
||||||
|
public Label notes;
|
||||||
|
public QCue activeCue;
|
||||||
|
public QSelectedCueCell()
|
||||||
|
{
|
||||||
|
|
||||||
|
Grid mainG = new Grid
|
||||||
|
{
|
||||||
|
Padding = new Thickness(0),
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition{Height = GridLength.Star},
|
||||||
|
new RowDefinition{Height = new GridLength(2, GridUnitType.Star)}
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Grid topGrid = new Grid
|
||||||
|
{
|
||||||
|
Padding = new Thickness(0),
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition{Height = GridLength.Auto}
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Grid bottomGrid = new Grid
|
||||||
|
{
|
||||||
|
Padding = new Thickness(0),
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition{Height = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new RowDefinition{Height = new GridLength(2,GridUnitType.Star)}
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)},
|
||||||
|
new ColumnDefinition{Width = new GridLength(1,GridUnitType.Star)}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
number = new Label {
|
||||||
|
Text = "",
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
|
FontSize = App.HeightUnit * 5
|
||||||
|
};
|
||||||
|
|
||||||
|
name = new Label {
|
||||||
|
Text = "Loading Workspace....",
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center,
|
||||||
|
FontSize = App.HeightUnit * 3.5,
|
||||||
|
Margin = new Thickness(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
type = new Label {
|
||||||
|
Text = QIcon.SPIN3,
|
||||||
|
FontFamily = App.QFont,
|
||||||
|
VerticalTextAlignment = TextAlignment.Center,
|
||||||
|
HorizontalTextAlignment = TextAlignment.End,
|
||||||
|
FontSize = App.HeightUnit * 5
|
||||||
|
};
|
||||||
|
|
||||||
|
notes = new Label {
|
||||||
|
Text = "Loading Cue Lists and Playhead Position",
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center,
|
||||||
|
Margin = new Thickness(0,0,0,10),
|
||||||
|
VerticalOptions = LayoutOptions.FillAndExpand,
|
||||||
|
HorizontalOptions = LayoutOptions.FillAndExpand
|
||||||
|
};
|
||||||
|
|
||||||
|
//BACKGROUND COLORS FOR TESTING ONLY
|
||||||
|
//notes.BackgroundColor = Color.Red;
|
||||||
|
//number.BackgroundColor = Color.Red;
|
||||||
|
//name.BackgroundColor = Color.Red;
|
||||||
|
//type.BackgroundColor = Color.Red;
|
||||||
|
//topGrid.BackgroundColor = Color.Green;
|
||||||
|
//bottomGrid.BackgroundColor = Color.Green;
|
||||||
|
|
||||||
|
topGrid.Add(number, 0, 0);
|
||||||
|
topGrid.Add(type, 4, 0);
|
||||||
|
Grid.SetColumnSpan(number, 3);
|
||||||
|
Grid.SetColumnSpan(type, 1);
|
||||||
|
|
||||||
|
bottomGrid.Add(name, 0, 0);
|
||||||
|
bottomGrid.Add(notes, 0, 1);
|
||||||
|
Grid.SetColumnSpan(name, 5);
|
||||||
|
Grid.SetColumnSpan(notes,5);
|
||||||
|
|
||||||
|
mainG.Add(topGrid, 0, 0);
|
||||||
|
Grid.SetColumnSpan(topGrid, 5);
|
||||||
|
|
||||||
|
mainG.Add(bottomGrid, 0, 1);
|
||||||
|
Grid.SetColumnSpan(bottomGrid, 5);
|
||||||
|
|
||||||
|
CornerRadius = 20;
|
||||||
|
BackgroundColor = Color.FromArgb("D8D8D8");
|
||||||
|
HeightRequest = App.HeightUnit * 25;
|
||||||
|
Margin = new Thickness(10);
|
||||||
|
Content = mainG;
|
||||||
|
|
||||||
|
SetupDoubleTapEdit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupDoubleTapEdit()
|
||||||
|
{
|
||||||
|
var notesDoubleTap = new TapGestureRecognizer();
|
||||||
|
var nameDoubleTap = new TapGestureRecognizer();
|
||||||
|
var numberDoubleTap = new TapGestureRecognizer();
|
||||||
|
|
||||||
|
notesDoubleTap.NumberOfTapsRequired = 2;
|
||||||
|
nameDoubleTap.NumberOfTapsRequired = 2;
|
||||||
|
numberDoubleTap.NumberOfTapsRequired = 2;
|
||||||
|
|
||||||
|
notesDoubleTap.Tapped += (s, e) =>
|
||||||
|
{
|
||||||
|
UserDialogs.Instance.Prompt(new PromptConfig
|
||||||
|
{
|
||||||
|
Title = "Update Notes",
|
||||||
|
Message = "Changes notes to update",
|
||||||
|
OkText = "Update",
|
||||||
|
Text = notes.Text,
|
||||||
|
OnAction = (qNotes) =>
|
||||||
|
{
|
||||||
|
if (!qNotes.Ok)
|
||||||
|
return;
|
||||||
|
OnSelectedCueEdited("notes", qNotes.Text);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
nameDoubleTap.Tapped += (s, e) =>
|
||||||
|
{
|
||||||
|
UserDialogs.Instance.Prompt(new PromptConfig
|
||||||
|
{
|
||||||
|
Title = "Update Name",
|
||||||
|
Message = "Change name to update",
|
||||||
|
OkText = "Update",
|
||||||
|
Text = name.Text,
|
||||||
|
OnAction = (qName) =>
|
||||||
|
{
|
||||||
|
if (!qName.Ok)
|
||||||
|
return;
|
||||||
|
OnSelectedCueEdited("name", qName.Text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
numberDoubleTap.Tapped += (s, e) =>
|
||||||
|
{
|
||||||
|
UserDialogs.Instance.Prompt(new PromptConfig
|
||||||
|
{
|
||||||
|
Title = "Update Number",
|
||||||
|
Message = "Change number to update",
|
||||||
|
OkText = "Update",
|
||||||
|
Text = number.Text,
|
||||||
|
OnAction = (qNumber) =>
|
||||||
|
{
|
||||||
|
if (!qNumber.Ok)
|
||||||
|
return;
|
||||||
|
OnSelectedCueEdited("number", qNumber.Text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
notes.GestureRecognizers.Add(notesDoubleTap);
|
||||||
|
name.GestureRecognizers.Add(nameDoubleTap);
|
||||||
|
number.GestureRecognizers.Add(numberDoubleTap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSelectedCue(QCue cue)
|
||||||
|
{
|
||||||
|
activeCue = cue;
|
||||||
|
name.Text = cue.listName;
|
||||||
|
number.Text = cue.number;
|
||||||
|
type.Text = cue.getIconString();
|
||||||
|
notes.Text = cue.notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnSelectedCueEdited(string prop, string value)
|
||||||
|
{
|
||||||
|
SelectedCueEdited?.Invoke(this, new CueEditArgs() { CueID = activeCue.uniqueID, Property = prop, NewValue = value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using qController.QItems;
|
||||||
|
|
||||||
|
namespace qController.Communication
|
||||||
|
{
|
||||||
|
public class CueEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public QCue Cue
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WorkspaceEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public QWorkspace UpdatedWorkspace
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlaybackPositionArgs : EventArgs
|
||||||
|
{
|
||||||
|
public string PlaybackPosition
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConnectEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public string Status
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string WorkspaceId
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WorkspaceInfoArgs : EventArgs
|
||||||
|
{
|
||||||
|
public List<QWorkspaceInfo> WorkspaceInfo
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChildrenEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public string cue_id
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
public List<QCue> children
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace qController.Communication
|
||||||
|
{
|
||||||
|
public delegate void SelectedCueUpdatedHandler(object source, CueEventArgs args);
|
||||||
|
public delegate void WorkspaceUpdatedHandler(object source, WorkspaceEventArgs args);
|
||||||
|
public delegate void CueInfoUpdatedHandler(object source, CueEventArgs args);
|
||||||
|
public delegate void PlaybackPositionUpdatedHandler(object source, PlaybackPositionArgs args);
|
||||||
|
public delegate void ConnectionStatusHandler(object source, ConnectEventArgs args);
|
||||||
|
public delegate void ChildrenUpdateHandler(object source, ChildrenEventArgs args);
|
||||||
|
public delegate void WorkspaceDisconnectHandler(object source, EventArgs args);
|
||||||
|
public delegate void WorkspaceInfoHandler(object source, WorkspaceInfoArgs args);
|
||||||
|
public delegate void WorkspaceLoadErrorHandler(object source, WorkspaceEventArgs args);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//IP Address verification
|
||||||
|
//IMPLEMENT A ISREACHABLE METHOD TO BE ABLE TO DISPLAY ONLINE QINSTANCES
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace qController.Communication
|
||||||
|
{
|
||||||
|
public class IPHelper
|
||||||
|
{
|
||||||
|
public IPHelper()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsValidAddress(string ipString){
|
||||||
|
if ((ipString.Split('.').Length - 1) != 3) return false;
|
||||||
|
IPAddress address;
|
||||||
|
return IPAddress.TryParse(ipString, out address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
//Class used to facilitate communication with a QInstance
|
||||||
|
//Listens for incoming messages via qReceiver (UDP) and tcpClient (TCP)
|
||||||
|
//TODO: WEIRD MESSAGE PARSING RULES NEED FIXED
|
||||||
|
using System;
|
||||||
|
using SharpOSC;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace qController.Communication
|
||||||
|
{
|
||||||
|
public class QClient
|
||||||
|
{
|
||||||
|
private TCPClient tcpClient;
|
||||||
|
public QParser qParser;
|
||||||
|
string Address;
|
||||||
|
int Port;
|
||||||
|
public bool connected;
|
||||||
|
|
||||||
|
public QClient(string address, int port)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
Port = port;
|
||||||
|
qParser = new QParser();
|
||||||
|
tcpClient = new TCPClient(Address, Port);
|
||||||
|
connected = tcpClient.Connect();
|
||||||
|
tcpClient.MessageReceived += OnMessageReceived;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//one method for messages received whether from TCP or UDP (SAME MSG FORMAT)
|
||||||
|
private void OnMessageReceived(object source, MessageEventArgs args)
|
||||||
|
{
|
||||||
|
Log.Debug("QCLIENT - Message Received: " + args.Message.Address);
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: find a better filtering process
|
||||||
|
if (!args.Message.Address.Contains("update"))
|
||||||
|
qParser.ParseMessage(args.Message);
|
||||||
|
else if (args.Message.Address.Contains("playbackPosition") || args.Message.Address.Contains("cueList") || args.Message.Address.Contains("dashboard"))
|
||||||
|
qParser.ParseMessage(args.Message);
|
||||||
|
else if (args.Message.Address.Contains("cue_id"))
|
||||||
|
{
|
||||||
|
var parts = args.Message.Address.Split('/');
|
||||||
|
UpdateSpecificCue(parts[3], parts[5]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ProcessUpdate(args.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendTCP(string address)
|
||||||
|
{
|
||||||
|
//Log.Debug($"QCLIENT - TCP Sent with address: {address}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tcpClient.Send(new OscMessage(address));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Debug("QCLIENT - Send and Receive Exception: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendTCP(string address, params object[] args)
|
||||||
|
{
|
||||||
|
//Log.Debug($"QCLIENT - TCP Sent with address: {address}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tcpClient.Send(new OscMessage(address, args));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Debug("QCLIENT - Send and Receive w/Args Exception: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void ProcessUpdate(OscMessage msg)
|
||||||
|
{
|
||||||
|
Log.Debug("QCLIENT - Process Update: " + msg.Address);
|
||||||
|
if (msg.Address.Contains("workspace"))
|
||||||
|
{
|
||||||
|
UpdateWorkspace("not yet implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSpecificCue(string workspace_id,string cue_id)
|
||||||
|
{
|
||||||
|
string valuesForKeys = "[\"number\",\"uniqueID\",\"flagged\",\"listName\",\"type\",\"colorName\",\"name\",\"armed\",\"displayName\",\"isBroken\",\"isLoaded\",\"isPaused\",\"isRunning\",\"preWait\",\"duration\",\"postWait\",\"translationX\",\"translationY\",\"opacity\",\"scaleX\",\"scaleY\",\"notes\",\"levels\"]";
|
||||||
|
string address = "/workspace/" + workspace_id + "/cue_id/" + cue_id + "/valuesForKeys";
|
||||||
|
sendTCP(address, valuesForKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSelectedCue(string workspace_id)
|
||||||
|
{
|
||||||
|
string valuesForKeys = "[\"number\",\"uniqueID\",\"flagged\",\"listName\",\"type\",\"colorName\",\"name\",\"armed\",\"displayName\",\"isBroken\",\"isLoaded\",\"isPaused\",\"isRunning\",\"preWait\",\"duration\",\"postWait\",\"translationX\",\"translationY\",\"opacity\",\"scaleX\",\"scaleY\",\"notes\",\"levels\"]";
|
||||||
|
string address = "/workspace/" + workspace_id + "/cue/selected/valuesForKeys";
|
||||||
|
sendTCP(address, valuesForKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateWorkspace(string ws_id)
|
||||||
|
{
|
||||||
|
Log.Debug("QCLIENT - Workspace needs to be updated: " + ws_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
tcpClient.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
//Class used for overall Workspace control (ONLY REPRESENTS ONE WORKSPACE)
|
||||||
|
//Contains all necessary items to facilitate communication (sending, receiving) to a QLab workspace
|
||||||
|
//Also contains the local QWorkspace object which stores all the information that is used in displaying
|
||||||
|
//TODO: STILL NEED TO WORK ON PASSWORD PROTECTED WORKSPACES
|
||||||
|
using Serilog;
|
||||||
|
using qController.QItems;
|
||||||
|
|
||||||
|
namespace qController.Communication
|
||||||
|
{
|
||||||
|
public class QController
|
||||||
|
{
|
||||||
|
|
||||||
|
public QUpdater qUpdater;
|
||||||
|
public QClient qClient;
|
||||||
|
public QWorkspace qWorkspace;
|
||||||
|
public string playbackPosition;
|
||||||
|
private string ipAddress;
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
public QController(string address, int port)
|
||||||
|
{
|
||||||
|
this.port = port;
|
||||||
|
this.ipAddress = address;
|
||||||
|
qClient = new QClient(ipAddress, port);
|
||||||
|
qUpdater = new QUpdater(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect(string workspace_id)
|
||||||
|
{
|
||||||
|
Log.Debug($"QCONTROLLER - Connect Called: {workspace_id}");
|
||||||
|
qWorkspace = new QWorkspace(workspace_id);
|
||||||
|
qClient.sendTCP("/workspace/"+workspace_id+"/connect");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect(string workspace_id, string passcode)
|
||||||
|
{
|
||||||
|
Log.Debug($"QCONTROLLER - Connect with Passcode Called: {workspace_id}:{passcode}");
|
||||||
|
qWorkspace = new QWorkspace(workspace_id);
|
||||||
|
qClient.sendTCP("/workspace/" + workspace_id + "/connect", passcode);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect(QWorkspace workspace)
|
||||||
|
{
|
||||||
|
if(workspace.passcode != null)
|
||||||
|
Connect(workspace.workspace_id, workspace.passcode);
|
||||||
|
else
|
||||||
|
Connect(workspace.workspace_id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void KickOff()
|
||||||
|
{
|
||||||
|
Log.Debug($"QCONTROLLER - Searching for workspaces on: {ipAddress}");
|
||||||
|
qClient.sendTCP("/workspaces");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
Log.Debug($"QCONTROLLER - Disconnecting from: {qWorkspace.workspace_id}");
|
||||||
|
qClient.sendTCP("/workspace/"+qWorkspace.workspace_id+"/disconnect");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resume()
|
||||||
|
{
|
||||||
|
//qClient = new QClient(ipAddress, port);
|
||||||
|
//qUpdater = new QUpdater(this);
|
||||||
|
if(qWorkspace != null)
|
||||||
|
{
|
||||||
|
Log.Debug($"QCONTROLLER - Resuming: {qWorkspace.workspace_id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Kill(){
|
||||||
|
Log.Debug("QCONTROLLER - Killing");
|
||||||
|
if (qClient != null)
|
||||||
|
qClient.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//Currently unused would like to reimplement eventually
|
||||||
|
//Currently being done in qConnectionPage.xaml.cs Scan() method
|
||||||
|
//TODO: Implement this along with a way of getting active Instances and # of workspaces for an instance
|
||||||
|
using Serilog;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Zeroconf;
|
||||||
|
|
||||||
|
namespace qController.Communication
|
||||||
|
{
|
||||||
|
public class QFinder
|
||||||
|
{
|
||||||
|
public QFinder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void SearchForWorkspaces()
|
||||||
|
{
|
||||||
|
Log.Debug("QFINDER - Scanning Started");
|
||||||
|
IReadOnlyList<IZeroconfHost> results = await ZeroconfResolver.ResolveAsync("_qlab._tcp.local.");
|
||||||
|
Log.Debug("QFINDER - Scanning Done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
using System;
|
||||||
|
using SharpOSC;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using qController.QItems;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace qController.Communication
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public class QParser
|
||||||
|
{
|
||||||
|
public QParser()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public event WorkspaceLoadErrorHandler WorkspaceLoadError;
|
||||||
|
public event WorkspaceInfoHandler WorkspaceInfoReceived;
|
||||||
|
public event WorkspaceDisconnectHandler WorkspaceDisconnect;
|
||||||
|
public event ChildrenUpdateHandler ChildrenUpdated;
|
||||||
|
public event ConnectionStatusHandler ConnectionStatusChanged;
|
||||||
|
public event SelectedCueUpdatedHandler SelectedCueUpdated;
|
||||||
|
public event WorkspaceUpdatedHandler WorkspaceUpdated;
|
||||||
|
public event CueInfoUpdatedHandler CueInfoUpdated;
|
||||||
|
public event PlaybackPositionUpdatedHandler PlaybackPositionUpdated;
|
||||||
|
|
||||||
|
public void ParseMessage(OscMessage msg){
|
||||||
|
if (!msg.Address.Contains("null"))
|
||||||
|
{
|
||||||
|
if (msg.Address.Contains("valuesForKeys"))
|
||||||
|
{
|
||||||
|
ParseCueUpdateInfo(msg);
|
||||||
|
}
|
||||||
|
else if (msg.Address.Contains("cueLists"))
|
||||||
|
ParseWorkspaceInfo(msg);
|
||||||
|
else if (msg.Address.Contains("playbackPosition"))
|
||||||
|
ParsePositionUpdateInfo(msg);
|
||||||
|
else if (msg.Address.Contains("thump"))
|
||||||
|
Log.Debug("QPARSER - Heartbeat Received");
|
||||||
|
else if (msg.Address.Contains("disconnect"))
|
||||||
|
OnWorkspaceDisconnect();
|
||||||
|
else if (msg.Address.Contains("connect"))
|
||||||
|
ParseConnectInfo(msg);
|
||||||
|
else if (msg.Address.Contains("children"))
|
||||||
|
ParseChildrenInfo(msg);
|
||||||
|
else if (msg.Address.Contains("workspaces"))
|
||||||
|
ParseQInfo(msg);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Debug("QPARSER - Unkown message type: " + msg.Address);
|
||||||
|
foreach (var item in msg.Arguments)
|
||||||
|
{
|
||||||
|
Log.Debug(item.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParseConnectInfo(OscMessage msg)
|
||||||
|
{
|
||||||
|
if (msg.Arguments.Count > 0)
|
||||||
|
{
|
||||||
|
JToken connectStatus = OSC2JSON(msg);
|
||||||
|
;
|
||||||
|
OnConnectionStatusChanged(connectStatus.ToString(), msg.Address.Split('/')[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParsePositionUpdateInfo(OscMessage msg)
|
||||||
|
{
|
||||||
|
if(msg.Arguments.Count > 0)
|
||||||
|
{
|
||||||
|
OnPlaybackPositionUpdated(msg.Arguments[0].ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParseCueUpdateInfo(OscMessage msg)
|
||||||
|
{
|
||||||
|
JToken cueUpdate = OSC2JSON(msg);
|
||||||
|
if(cueUpdate != null)
|
||||||
|
{
|
||||||
|
QCue cue = JsonConvert.DeserializeObject<QCue>(cueUpdate.ToString());
|
||||||
|
OnCueInfoUpdated(cue);
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
Log.Debug("QPARSER - cue update info does not have any data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParseSelectedCueInfo(OscMessage msg){
|
||||||
|
JToken selectedCue = OSC2JSON(msg);
|
||||||
|
if (selectedCue != null)
|
||||||
|
{
|
||||||
|
QCue cue = JsonConvert.DeserializeObject<QCue>(selectedCue.ToString());
|
||||||
|
OnSelectedCueUpdated(cue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Debug("QPARSER - selected does not have any data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParseQInfo(OscMessage msg)
|
||||||
|
{
|
||||||
|
JToken qInfo = OSC2JSON(msg);
|
||||||
|
if (qInfo != null)
|
||||||
|
{
|
||||||
|
List<QWorkspaceInfo> qWorkspaceInfo = JsonConvert.DeserializeObject<List<QWorkspaceInfo>>(qInfo.ToString());
|
||||||
|
OnWorkspaceInfoReceived(qWorkspaceInfo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Debug("QPARSER - selected does not have any data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParseWorkspaceInfo(OscMessage msg)
|
||||||
|
{
|
||||||
|
if (msg.Arguments.Count > 0)
|
||||||
|
{
|
||||||
|
var parts = msg.Address.Split('/');
|
||||||
|
string id = parts[3];
|
||||||
|
|
||||||
|
if (msg.Arguments[0].ToString() != "")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QWorkspace workspace = JsonConvert.DeserializeObject<QWorkspace>(msg.Arguments[0].ToString());
|
||||||
|
OnWorkspaceUpdated(workspace);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Debug($"QPARSER - Workspace Load Error: {ex.ToString()}");
|
||||||
|
OnWorkspaceLoadError(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnWorkspaceLoadError(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParseChildrenInfo(OscMessage msg)
|
||||||
|
{
|
||||||
|
if (msg.Arguments.Count > 0)
|
||||||
|
{
|
||||||
|
string cue_id = msg.Address.Split('/')[3];
|
||||||
|
JToken children_obj = OSC2JSON(msg);
|
||||||
|
if(children_obj != null)
|
||||||
|
{
|
||||||
|
List<QCue> children = JsonConvert.DeserializeObject<List<QCue>>(children_obj.ToString());
|
||||||
|
OnChildrenUpdated(cue_id, children);
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
Log.Debug("QPARSER - children info message does not have any data");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JToken OSC2JSON(OscMessage Msg){
|
||||||
|
JObject json = JObject.Parse(Msg.Arguments.ToArray()[0].ToString());
|
||||||
|
return json.GetValue("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnWorkspaceUpdated(QWorkspace workspace)
|
||||||
|
{
|
||||||
|
WorkspaceUpdated?.Invoke(this, new WorkspaceEventArgs() { UpdatedWorkspace = workspace });
|
||||||
|
}
|
||||||
|
protected virtual void OnSelectedCueUpdated(QCue cue)
|
||||||
|
{
|
||||||
|
SelectedCueUpdated?.Invoke(this, new CueEventArgs() { Cue = cue });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnCueInfoUpdated(QCue cue)
|
||||||
|
{
|
||||||
|
CueInfoUpdated?.Invoke(this, new CueEventArgs() { Cue = cue });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPlaybackPositionUpdated(string id)
|
||||||
|
{
|
||||||
|
Log.Error(id);
|
||||||
|
PlaybackPositionUpdated?.Invoke(this, new PlaybackPositionArgs() { PlaybackPosition = id });
|
||||||
|
}
|
||||||
|
protected virtual void OnConnectionStatusChanged(string status, string workspace_id)
|
||||||
|
{
|
||||||
|
ConnectionStatusChanged?.Invoke(this, new ConnectEventArgs() { Status = status, WorkspaceId = workspace_id });
|
||||||
|
}
|
||||||
|
protected virtual void OnChildrenUpdated(string id, List<QCue> cues)
|
||||||
|
{
|
||||||
|
ChildrenUpdated?.Invoke(this, new ChildrenEventArgs() { cue_id = id, children = cues });
|
||||||
|
}
|
||||||
|
protected virtual void OnWorkspaceDisconnect()
|
||||||
|
{
|
||||||
|
WorkspaceDisconnect?.Invoke(this, new EventArgs());
|
||||||
|
}
|
||||||
|
protected virtual void OnWorkspaceInfoReceived(List<QWorkspaceInfo> workspaces)
|
||||||
|
{
|
||||||
|
WorkspaceInfoReceived?.Invoke(this, new WorkspaceInfoArgs() { WorkspaceInfo = workspaces });
|
||||||
|
}
|
||||||
|
protected virtual void OnWorkspaceLoadError(string id)
|
||||||
|
{
|
||||||
|
WorkspaceLoadError?.Invoke(this, new WorkspaceEventArgs { UpdatedWorkspace = new QWorkspace(id) });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
//Class used for updating local "copy" of workspace info, cue-lists, cues, etc.
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace qController.Communication
|
||||||
|
{
|
||||||
|
public class QUpdater
|
||||||
|
{
|
||||||
|
private QController qController;
|
||||||
|
|
||||||
|
public QUpdater(QController controller)
|
||||||
|
{
|
||||||
|
qController = controller;
|
||||||
|
qController.qClient.qParser.CueInfoUpdated += OnCueUpdateReceived;
|
||||||
|
qController.qClient.qParser.WorkspaceUpdated += OnWorkspaceUpdated;
|
||||||
|
qController.qClient.qParser.PlaybackPositionUpdated += OnPlaybackPositionUpdated;
|
||||||
|
qController.qClient.qParser.WorkspaceLoadError += OnWorkspaceLoadError;
|
||||||
|
qController.qClient.qParser.ConnectionStatusChanged += OnConnectionStatusChanged;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConnectionStatusChanged(object source, ConnectEventArgs args)
|
||||||
|
{
|
||||||
|
|
||||||
|
Log.Debug("QUPDATER - Connection Status Changed: " + args.Status);
|
||||||
|
|
||||||
|
if (args.Status.Contains("ok"))
|
||||||
|
{
|
||||||
|
//new sort of connect format for QLab 5 ok:view|control|edit is connection good
|
||||||
|
if(args.Status.Contains(":") && !args.Status.Contains("view"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qController.qClient.sendTCP("/workspace/" + qController.qWorkspace.workspace_id + "/updates", 1);
|
||||||
|
qController.qClient.sendTCP("/workspace/" + qController.qWorkspace.workspace_id + "/cueLists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWorkspaceLoadError(object source, WorkspaceEventArgs args)
|
||||||
|
{
|
||||||
|
Log.Debug("QUPDATER - Loading cuelists has failed for some reason retrying");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCueUpdateReceived(object source, CueEventArgs args)
|
||||||
|
{
|
||||||
|
qController.qWorkspace.UpdateCue(args.Cue);
|
||||||
|
|
||||||
|
if (args.Cue.type == "Group")
|
||||||
|
{
|
||||||
|
//Log.Debug("QUpdater/Updated cue was group cue, sending children request");
|
||||||
|
qController.qClient.sendTCP("/workspace/"+qController.qWorkspace.workspace_id+"/cue_id/" + args.Cue.uniqueID + "/children");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnWorkspaceUpdated(object source, WorkspaceEventArgs args)
|
||||||
|
{
|
||||||
|
qController.qWorkspace = args.UpdatedWorkspace;
|
||||||
|
qController.playbackPosition = null;
|
||||||
|
qController.qWorkspace.CheckPopulated();
|
||||||
|
if (qController.qWorkspace.IsPopulated)
|
||||||
|
{
|
||||||
|
Log.Debug("QUPDATER - Workspace group cues are already populated");
|
||||||
|
App.showToast("Workspace cues have been loaded....");
|
||||||
|
//get selected cue
|
||||||
|
qController.qClient.UpdateSelectedCue(qController.qWorkspace.workspace_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPlaybackPositionUpdated(object source, PlaybackPositionArgs args)
|
||||||
|
{
|
||||||
|
qController.playbackPosition = args.PlaybackPosition;
|
||||||
|
Log.Debug("QUPDATER - Update Specific Cue Called because of Playback Position Updated");
|
||||||
|
qController.qClient.UpdateSpecificCue(qController.qWorkspace.workspace_id,args.PlaybackPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
internal static class Extensions
|
||||||
|
{
|
||||||
|
public static int FirstIndexAfter<T>(this IEnumerable<T> items, int start, Func<T, bool> predicate)
|
||||||
|
{
|
||||||
|
if (items == null) throw new ArgumentNullException("items");
|
||||||
|
if (predicate == null) throw new ArgumentNullException("predicate");
|
||||||
|
if (start >= items.Count()) throw new ArgumentOutOfRangeException("start");
|
||||||
|
|
||||||
|
int retVal = 0;
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (retVal >= start && predicate(item)) return retVal;
|
||||||
|
retVal++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<List<T>> Split<T>(this IEnumerable<T> data, Func<T, bool> predicate)
|
||||||
|
{
|
||||||
|
var output = new List<List<T>>();
|
||||||
|
var curr = new List<T>();
|
||||||
|
output.Add(curr);
|
||||||
|
foreach (var x in data)
|
||||||
|
{
|
||||||
|
if (predicate(x))
|
||||||
|
{
|
||||||
|
curr = new List<T>();
|
||||||
|
output.Add(curr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
curr.Add(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static T[] SubArray<T>(this T[] data, int index, int length)
|
||||||
|
{
|
||||||
|
T[] result = new T[length];
|
||||||
|
Array.Copy(data, index, result, 0, length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public struct Midi
|
||||||
|
{
|
||||||
|
public byte Port;
|
||||||
|
public byte Status;
|
||||||
|
public byte Data1;
|
||||||
|
public byte Data2;
|
||||||
|
|
||||||
|
public Midi(byte port, byte status, byte data1, byte data2)
|
||||||
|
{
|
||||||
|
this.Port = port;
|
||||||
|
this.Status = status;
|
||||||
|
this.Data1 = data1;
|
||||||
|
this.Data2 = data2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(System.Object obj)
|
||||||
|
{
|
||||||
|
if (obj.GetType() == typeof(Midi))
|
||||||
|
{
|
||||||
|
if (this.Port == ((Midi)obj).Port && this.Status == ((Midi)obj).Status && this.Data1 == ((Midi)obj).Data1 && this.Data2 == ((Midi)obj).Data2)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (obj.GetType() == typeof(byte[]))
|
||||||
|
{
|
||||||
|
if (this.Port == ((byte[])obj)[0] && this.Status == ((byte[])obj)[1] && this.Data1 == ((byte[])obj)[2] && this.Data2 == ((byte[])obj)[3])
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Midi a, Midi b)
|
||||||
|
{
|
||||||
|
if (a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Midi a, Midi b)
|
||||||
|
{
|
||||||
|
if (!a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return (Port << 24) + (Status << 16) + (Data1 << 8) + (Data2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class OscBundle : OscPacket
|
||||||
|
{
|
||||||
|
Timetag _timetag;
|
||||||
|
|
||||||
|
public UInt64 Timetag
|
||||||
|
{
|
||||||
|
get { return _timetag.Tag; }
|
||||||
|
set { _timetag.Tag = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime Timestamp
|
||||||
|
{
|
||||||
|
get { return _timetag.Timestamp; }
|
||||||
|
set { _timetag.Timestamp = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OscMessage> Messages;
|
||||||
|
|
||||||
|
public OscBundle(UInt64 timetag, params OscMessage[] args)
|
||||||
|
{
|
||||||
|
_timetag = new Timetag(timetag);
|
||||||
|
Messages = new List<OscMessage>();
|
||||||
|
Messages.AddRange(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override byte[] GetBytes()
|
||||||
|
{
|
||||||
|
string bundle = "#bundle";
|
||||||
|
int bundleTagLen = Utils.AlignedStringLength(bundle);
|
||||||
|
byte[] tag = setULong(_timetag.Tag);
|
||||||
|
|
||||||
|
List<byte[]> outMessages = new List<byte[]>();
|
||||||
|
foreach (OscMessage msg in Messages)
|
||||||
|
{
|
||||||
|
outMessages.Add(msg.GetBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = bundleTagLen + tag.Length + outMessages.Sum(x => x.Length + 4);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
byte[] output = new byte[len];
|
||||||
|
Encoding.UTF8.GetBytes(bundle).CopyTo(output, i);
|
||||||
|
i += bundleTagLen;
|
||||||
|
tag.CopyTo(output, i);
|
||||||
|
i += tag.Length;
|
||||||
|
|
||||||
|
foreach (byte[] msg in outMessages)
|
||||||
|
{
|
||||||
|
var size = setInt(msg.Length);
|
||||||
|
size.CopyTo(output, i);
|
||||||
|
i += size.Length;
|
||||||
|
|
||||||
|
msg.CopyTo(output, i);
|
||||||
|
i += msg.Length; // msg size is always a multiple of 4
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class OscMessage : OscPacket
|
||||||
|
{
|
||||||
|
public string Address;
|
||||||
|
public List<object> Arguments;
|
||||||
|
|
||||||
|
public OscMessage(string address, params object[] args)
|
||||||
|
{
|
||||||
|
this.Address = address;
|
||||||
|
Arguments = new List<object>();
|
||||||
|
Arguments.AddRange(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "Address: " + Address + " Args: " + string.Join(",", Arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override byte[] GetBytes()
|
||||||
|
{
|
||||||
|
List<byte[]> parts = new List<byte[]>();
|
||||||
|
|
||||||
|
List<object> currentList = Arguments;
|
||||||
|
int ArgumentsIndex = 0;
|
||||||
|
|
||||||
|
string typeString = ",";
|
||||||
|
int i = 0;
|
||||||
|
while (i < currentList.Count)
|
||||||
|
{
|
||||||
|
var arg = currentList[i];
|
||||||
|
|
||||||
|
string type = (arg != null) ? arg.GetType().ToString() : "null";
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case "System.Int32":
|
||||||
|
typeString += "i";
|
||||||
|
parts.Add(setInt((int)arg));
|
||||||
|
break;
|
||||||
|
case "System.Single":
|
||||||
|
if (float.IsPositiveInfinity((float)arg))
|
||||||
|
{
|
||||||
|
typeString += "I";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
typeString += "f";
|
||||||
|
parts.Add(setFloat((float)arg));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "System.String":
|
||||||
|
typeString += "s";
|
||||||
|
parts.Add(setString((string)arg));
|
||||||
|
break;
|
||||||
|
case "System.Byte[]":
|
||||||
|
typeString += "b";
|
||||||
|
parts.Add(setBlob((byte[])arg));
|
||||||
|
break;
|
||||||
|
case "System.Int64":
|
||||||
|
typeString += "h";
|
||||||
|
parts.Add(setLong((Int64)arg));
|
||||||
|
break;
|
||||||
|
case "System.UInt64":
|
||||||
|
typeString += "t";
|
||||||
|
parts.Add(setULong((UInt64)arg));
|
||||||
|
break;
|
||||||
|
case "SharpOSC.Timetag":
|
||||||
|
typeString += "t";
|
||||||
|
parts.Add(setULong(((Timetag)arg).Tag));
|
||||||
|
break;
|
||||||
|
case "System.Double":
|
||||||
|
if (Double.IsPositiveInfinity((double)arg))
|
||||||
|
{
|
||||||
|
typeString += "I";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
typeString += "d";
|
||||||
|
parts.Add(setDouble((double)arg));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "SharpOSC.Symbol":
|
||||||
|
typeString += "S";
|
||||||
|
parts.Add(setString(((Symbol)arg).Value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "System.Char":
|
||||||
|
typeString += "c";
|
||||||
|
parts.Add(setChar((char)arg));
|
||||||
|
break;
|
||||||
|
case "SharpOSC.RGBA":
|
||||||
|
typeString += "r";
|
||||||
|
parts.Add(setRGBA((RGBA)arg));
|
||||||
|
break;
|
||||||
|
case "SharpOSC.Midi":
|
||||||
|
typeString += "m";
|
||||||
|
parts.Add(setMidi((Midi)arg));
|
||||||
|
break;
|
||||||
|
case "System.Boolean":
|
||||||
|
typeString += ((bool)arg) ? "T" : "F";
|
||||||
|
break;
|
||||||
|
case "null":
|
||||||
|
typeString += "N";
|
||||||
|
break;
|
||||||
|
|
||||||
|
// This part handles arrays. It points currentList to the array and resets i
|
||||||
|
// The array is processed like normal and when it is finished we replace
|
||||||
|
// currentList back with Arguments and continue from where we left off
|
||||||
|
case "System.Object[]":
|
||||||
|
case "System.Collections.Generic.List`1[System.Object]":
|
||||||
|
if(arg.GetType() == typeof(object[]))
|
||||||
|
arg = ((object[])arg).ToList();
|
||||||
|
|
||||||
|
if (Arguments != currentList)
|
||||||
|
throw new Exception("Nested Arrays are not supported");
|
||||||
|
typeString += "[";
|
||||||
|
currentList = (List<object>)arg;
|
||||||
|
ArgumentsIndex = i;
|
||||||
|
i = 0;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception("Unable to transmit values of type " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
if (currentList != Arguments && i == currentList.Count)
|
||||||
|
{
|
||||||
|
// End of array, go back to main Argument list
|
||||||
|
typeString += "]";
|
||||||
|
currentList = Arguments;
|
||||||
|
i = ArgumentsIndex+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int addressLen = (Address.Length == 0 || Address == null ) ? 0 : Utils.AlignedStringLength(Address);
|
||||||
|
int typeLen = Utils.AlignedStringLength(typeString);
|
||||||
|
|
||||||
|
int total = addressLen + typeLen + parts.Sum(x => x.Length);
|
||||||
|
|
||||||
|
byte[] output = new byte[total];
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
Encoding.UTF8.GetBytes(Address).CopyTo(output, i);
|
||||||
|
i += addressLen;
|
||||||
|
|
||||||
|
Encoding.UTF8.GetBytes(typeString).CopyTo(output, i);
|
||||||
|
i += typeLen;
|
||||||
|
|
||||||
|
foreach (byte[] part in parts)
|
||||||
|
{
|
||||||
|
part.CopyTo(output, i);
|
||||||
|
i += part.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public abstract class OscPacket
|
||||||
|
{
|
||||||
|
public static OscPacket GetPacket(byte[] OscData)
|
||||||
|
{
|
||||||
|
if (OscData[0] == '#')
|
||||||
|
return parseBundle(OscData);
|
||||||
|
else
|
||||||
|
return parseMessage(OscData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OscMessage GetMessage(byte[] OscData)
|
||||||
|
{
|
||||||
|
return parseMessage(OscData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract byte[] GetBytes();
|
||||||
|
|
||||||
|
#region Parse OSC packages
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes in an OSC bundle package in byte form and parses it into a more usable OscBundle object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <returns>Message containing various arguments and an address</returns>
|
||||||
|
private static OscMessage parseMessage(byte[] msg)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
//
|
||||||
|
//("Raw ASCII DATA: " + System.Text.Encoding.ASCII.GetString(msg));
|
||||||
|
//Log.Debug("Raw UTF-8 DATA: " + System.Text.Encoding.UTF8.GetString(msg));
|
||||||
|
string address = null;
|
||||||
|
char[] types = new char[0];
|
||||||
|
List<object> arguments = new List<object>();
|
||||||
|
List<object> mainArray = arguments; // used as a reference when we are parsing arrays to get the main array back
|
||||||
|
// Get address
|
||||||
|
address = getAddress(msg, index);
|
||||||
|
index += msg.FirstIndexAfter(address.Length, x => x == ',');
|
||||||
|
|
||||||
|
if (index % 4 != 0)
|
||||||
|
throw new Exception("Misaligned OSC Packet data. Address string is not padded correctly and does not align to 4 byte interval");
|
||||||
|
|
||||||
|
// Get type tags
|
||||||
|
types = getTypes(msg, index);
|
||||||
|
index += types.Length;
|
||||||
|
|
||||||
|
while (index % 4 != 0)
|
||||||
|
index++;
|
||||||
|
|
||||||
|
bool commaParsed = false;
|
||||||
|
|
||||||
|
foreach (char type in types)
|
||||||
|
{
|
||||||
|
// skip leading comma
|
||||||
|
if (type == ',' && !commaParsed)
|
||||||
|
{
|
||||||
|
commaParsed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(type)
|
||||||
|
{
|
||||||
|
case ('\0'):
|
||||||
|
break;
|
||||||
|
|
||||||
|
case('i'):
|
||||||
|
int intVal = getInt(msg, index);
|
||||||
|
arguments.Add(intVal);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case('f'):
|
||||||
|
float floatVal = getFloat(msg, index);
|
||||||
|
arguments.Add(floatVal);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case('s'):
|
||||||
|
string stringVal = getString(msg, index);
|
||||||
|
arguments.Add(stringVal);
|
||||||
|
index += stringVal.Length;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case('b'):
|
||||||
|
byte[] blob = getBlob(msg, index);
|
||||||
|
arguments.Add(blob);
|
||||||
|
index += 4 + blob.Length;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('h'):
|
||||||
|
Int64 hval = getLong(msg, index);
|
||||||
|
arguments.Add(hval);
|
||||||
|
index += 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('t'):
|
||||||
|
UInt64 sval = getULong(msg, index);
|
||||||
|
arguments.Add(new Timetag(sval));
|
||||||
|
index += 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('d'):
|
||||||
|
double dval = getDouble(msg, index);
|
||||||
|
arguments.Add(dval);
|
||||||
|
index += 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('S'):
|
||||||
|
string SymbolVal = getString(msg, index);
|
||||||
|
arguments.Add(new Symbol(SymbolVal));
|
||||||
|
index += SymbolVal.Length;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('c'):
|
||||||
|
char cval = getChar(msg, index);
|
||||||
|
arguments.Add(cval);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('r'):
|
||||||
|
RGBA rgbaval = getRGBA(msg, index);
|
||||||
|
arguments.Add(rgbaval);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('m'):
|
||||||
|
Midi midival = getMidi(msg, index);
|
||||||
|
arguments.Add(midival);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('T'):
|
||||||
|
arguments.Add(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('F'):
|
||||||
|
arguments.Add(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('N'):
|
||||||
|
arguments.Add(null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('I'):
|
||||||
|
arguments.Add(double.PositiveInfinity);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('['):
|
||||||
|
if (arguments != mainArray)
|
||||||
|
throw new Exception("SharpOSC does not support nested arrays");
|
||||||
|
arguments = new List<object>(); // make arguments point to a new object array
|
||||||
|
break;
|
||||||
|
|
||||||
|
case (']'):
|
||||||
|
mainArray.Add(arguments); // add the array to the main array
|
||||||
|
arguments = mainArray; // make arguments point back to the main array
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception("OSC type tag '" + type + "' is unknown.");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index % 4 != 0)
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OscMessage(address, arguments.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes in an OSC bundle package in byte form and parses it into a more usable OscBundle object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bundle"></param>
|
||||||
|
/// <returns>Bundle containing elements and a timetag</returns>
|
||||||
|
private static OscBundle parseBundle(byte[] bundle)
|
||||||
|
{
|
||||||
|
UInt64 timetag;
|
||||||
|
List<OscMessage> messages = new List<OscMessage>();
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
var bundleTag = Encoding.UTF8.GetString(bundle.SubArray(0, 8));
|
||||||
|
index += 8;
|
||||||
|
|
||||||
|
timetag = getULong(bundle, index);
|
||||||
|
index += 8;
|
||||||
|
|
||||||
|
if (bundleTag != "#bundle\0")
|
||||||
|
throw new Exception("Not a bundle");
|
||||||
|
|
||||||
|
while (index < bundle.Length)
|
||||||
|
{
|
||||||
|
int size = getInt(bundle, index);
|
||||||
|
index += 4;
|
||||||
|
|
||||||
|
byte[] messageBytes = bundle.SubArray(index, size);
|
||||||
|
var message = parseMessage(messageBytes);
|
||||||
|
|
||||||
|
messages.Add(message);
|
||||||
|
|
||||||
|
index += size;
|
||||||
|
while (index % 4 != 0)
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
OscBundle output = new OscBundle(timetag, messages.ToArray());
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get arguments from byte array
|
||||||
|
|
||||||
|
private static string getAddress(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
int i = index;
|
||||||
|
string address = "";
|
||||||
|
for (; i < msg.Length; i += 4)
|
||||||
|
{
|
||||||
|
if (msg[i] == ',')
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
address = Encoding.UTF8.GetString(msg.SubArray(index, i - 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= msg.Length && address == null)
|
||||||
|
throw new Exception("no comma found");
|
||||||
|
|
||||||
|
return address.Replace("\0", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char[] getTypes(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
int i = index + 4;
|
||||||
|
char[] types = null;
|
||||||
|
|
||||||
|
for (; i < msg.Length; i += 4)
|
||||||
|
{
|
||||||
|
if (msg[i - 1] == 0)
|
||||||
|
{
|
||||||
|
types = Encoding.UTF8.GetChars(msg.SubArray(index, i - index));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types == null)
|
||||||
|
{
|
||||||
|
byte[] term = { 0 };
|
||||||
|
types = Encoding.UTF8.GetChars(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= msg.Length && types == null)
|
||||||
|
throw new Exception("No null terminator after type string");
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
int val = (msg[index] << 24) + (msg[index + 1] << 16) + (msg[index + 2] << 8) + (msg[index + 3] << 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float getFloat(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
byte[] reversed = new byte[4];
|
||||||
|
reversed[3] = msg[index];
|
||||||
|
reversed[2] = msg[index+1];
|
||||||
|
reversed[1] = msg[index+2];
|
||||||
|
reversed[0] = msg[index + 3];
|
||||||
|
float val = System.BitConverter.ToSingle(reversed, 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string getString(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
string output = null;
|
||||||
|
int i = index + 4;
|
||||||
|
for (; (i-1) < msg.Length; i += 4)
|
||||||
|
{
|
||||||
|
if (msg[i - 1] == 0)
|
||||||
|
{
|
||||||
|
output = Encoding.UTF8.GetString(msg.SubArray(index, i - index));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (output == null)
|
||||||
|
{
|
||||||
|
byte[] term = { 0 };
|
||||||
|
output = Encoding.UTF8.GetString(term);
|
||||||
|
}
|
||||||
|
if (i >= msg.Length && output == null)
|
||||||
|
throw new Exception("No null terminator after type string");
|
||||||
|
|
||||||
|
return output.Replace("\0", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getBlob(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
int size = getInt(msg, index);
|
||||||
|
return msg.SubArray(index + 4, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UInt64 getULong(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
UInt64 val = ((UInt64)msg[index] << 56) + ((UInt64)msg[index + 1] << 48) + ((UInt64)msg[index + 2] << 40) + ((UInt64)msg[index + 3] << 32)
|
||||||
|
+ ((UInt64)msg[index + 4] << 24) + ((UInt64)msg[index + 5] << 16) + ((UInt64)msg[index + 6] << 8) + ((UInt64)msg[index + 7] << 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Int64 getLong(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
byte[] var = new byte[8];
|
||||||
|
var[7] = msg[index];
|
||||||
|
var[6] = msg[index+1];
|
||||||
|
var[5] = msg[index+2];
|
||||||
|
var[4] = msg[index+3];
|
||||||
|
var[3] = msg[index+4];
|
||||||
|
var[2] = msg[index+5];
|
||||||
|
var[1] = msg[index+6];
|
||||||
|
var[0] = msg[index+7];
|
||||||
|
|
||||||
|
Int64 val = BitConverter.ToInt64(var, 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double getDouble(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
byte[] var = new byte[8];
|
||||||
|
var[7] = msg[index];
|
||||||
|
var[6] = msg[index + 1];
|
||||||
|
var[5] = msg[index + 2];
|
||||||
|
var[4] = msg[index + 3];
|
||||||
|
var[3] = msg[index + 4];
|
||||||
|
var[2] = msg[index + 5];
|
||||||
|
var[1] = msg[index + 6];
|
||||||
|
var[0] = msg[index + 7];
|
||||||
|
|
||||||
|
double val = BitConverter.ToDouble(var, 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char getChar(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
return (char)msg[index + 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RGBA getRGBA(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
return new RGBA(msg[index], msg[index + 1], msg[index + 2], msg[index + 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Midi getMidi(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
return new Midi(msg[index], msg[index + 1], msg[index + 2], msg[index + 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Create byte arrays for arguments
|
||||||
|
|
||||||
|
protected static byte[] setInt(int value)
|
||||||
|
{
|
||||||
|
byte[] msg = new byte[4];
|
||||||
|
|
||||||
|
var bytes = BitConverter.GetBytes(value);
|
||||||
|
msg[0] = bytes[3];
|
||||||
|
msg[1] = bytes[2];
|
||||||
|
msg[2] = bytes[1];
|
||||||
|
msg[3] = bytes[0];
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setFloat(float value)
|
||||||
|
{
|
||||||
|
byte[] msg = new byte[4];
|
||||||
|
|
||||||
|
var bytes = BitConverter.GetBytes(value);
|
||||||
|
msg[0] = bytes[3];
|
||||||
|
msg[1] = bytes[2];
|
||||||
|
msg[2] = bytes[1];
|
||||||
|
msg[3] = bytes[0];
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setString(string value)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(value);
|
||||||
|
int len = bytes.Length + (4 - bytes.Length % 4);
|
||||||
|
if (len <= bytes.Length) len = len + 4;
|
||||||
|
|
||||||
|
byte[] msg = new byte[len];
|
||||||
|
bytes.CopyTo(msg, 0);
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setBlob(byte[] value)
|
||||||
|
{
|
||||||
|
int len = value.Length + 4;
|
||||||
|
len = len + (4 - len % 4);
|
||||||
|
|
||||||
|
byte[] msg = new byte[len];
|
||||||
|
byte[] size = setInt(value.Length);
|
||||||
|
size.CopyTo(msg, 0);
|
||||||
|
value.CopyTo(msg, 4);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setLong(Int64 value)
|
||||||
|
{
|
||||||
|
byte[] rev = BitConverter.GetBytes(value);
|
||||||
|
byte[] output = new byte[8];
|
||||||
|
output[0] = rev[7];
|
||||||
|
output[1] = rev[6];
|
||||||
|
output[2] = rev[5];
|
||||||
|
output[3] = rev[4];
|
||||||
|
output[4] = rev[3];
|
||||||
|
output[5] = rev[2];
|
||||||
|
output[6] = rev[1];
|
||||||
|
output[7] = rev[0];
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setULong(UInt64 value)
|
||||||
|
{
|
||||||
|
byte[] rev = BitConverter.GetBytes(value);
|
||||||
|
byte[] output = new byte[8];
|
||||||
|
output[0] = rev[7];
|
||||||
|
output[1] = rev[6];
|
||||||
|
output[2] = rev[5];
|
||||||
|
output[3] = rev[4];
|
||||||
|
output[4] = rev[3];
|
||||||
|
output[5] = rev[2];
|
||||||
|
output[6] = rev[1];
|
||||||
|
output[7] = rev[0];
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setDouble(double value)
|
||||||
|
{
|
||||||
|
byte[] rev = BitConverter.GetBytes(value);
|
||||||
|
byte[] output = new byte[8];
|
||||||
|
output[0] = rev[7];
|
||||||
|
output[1] = rev[6];
|
||||||
|
output[2] = rev[5];
|
||||||
|
output[3] = rev[4];
|
||||||
|
output[4] = rev[3];
|
||||||
|
output[5] = rev[2];
|
||||||
|
output[6] = rev[1];
|
||||||
|
output[7] = rev[0];
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setChar(char value)
|
||||||
|
{
|
||||||
|
byte[] output = new byte[4];
|
||||||
|
output[0] = 0;
|
||||||
|
output[1] = 0;
|
||||||
|
output[2] = 0;
|
||||||
|
output[3] = (byte)value;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setRGBA(RGBA value)
|
||||||
|
{
|
||||||
|
byte[] output = new byte[4];
|
||||||
|
output[0] = value.Red;
|
||||||
|
output[1] = value.Green;
|
||||||
|
output[2] = value.Blue;
|
||||||
|
output[3] = value.Alpha;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setMidi(Midi value)
|
||||||
|
{
|
||||||
|
byte[] output = new byte[4];
|
||||||
|
output[0] = value.Port;
|
||||||
|
output[1] = value.Status;
|
||||||
|
output[2] = value.Data1;
|
||||||
|
output[3] = value.Data2;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public struct RGBA
|
||||||
|
{
|
||||||
|
public byte Red;
|
||||||
|
public byte Green;
|
||||||
|
public byte Blue;
|
||||||
|
public byte Alpha;
|
||||||
|
|
||||||
|
public RGBA(byte red, byte green, byte blue, byte alpha)
|
||||||
|
{
|
||||||
|
this.Red = red;
|
||||||
|
this.Green = green;
|
||||||
|
this.Blue = blue;
|
||||||
|
this.Alpha = alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(System.Object obj)
|
||||||
|
{
|
||||||
|
if (obj.GetType() == typeof(RGBA))
|
||||||
|
{
|
||||||
|
if (this.Red == ((RGBA)obj).Red && this.Green == ((RGBA)obj).Green && this.Blue == ((RGBA)obj).Blue && this.Alpha == ((RGBA)obj).Alpha)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (obj.GetType() == typeof(byte[]))
|
||||||
|
{
|
||||||
|
if (this.Red == ((byte[])obj)[0] && this.Green == ((byte[])obj)[1] && this.Blue == ((byte[])obj)[2] && this.Alpha == ((byte[])obj)[3])
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(RGBA a, RGBA b)
|
||||||
|
{
|
||||||
|
if (a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(RGBA a, RGBA b)
|
||||||
|
{
|
||||||
|
if (!a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return (Red << 24) + (Green << 16) + (Blue << 8) + (Alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public static class SlipFrame
|
||||||
|
{
|
||||||
|
public static readonly byte END = 0xc0;
|
||||||
|
public static readonly byte ESC = 0xdb;
|
||||||
|
public static readonly byte ESC_END = 0xDC;
|
||||||
|
public static readonly byte ESC_ESC = 0xDD;
|
||||||
|
private static List<byte> buffer = new List<byte>();
|
||||||
|
|
||||||
|
public static List<byte[]> Decode(byte[] data)
|
||||||
|
{
|
||||||
|
List<byte[]> messages = new List<byte[]>();
|
||||||
|
|
||||||
|
bool escapeNext = false;
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
{
|
||||||
|
if (data[i] == ESC)
|
||||||
|
{
|
||||||
|
escapeNext = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (escapeNext)
|
||||||
|
{
|
||||||
|
if (data[i] == ESC_END)
|
||||||
|
{
|
||||||
|
buffer.Add(END);
|
||||||
|
}
|
||||||
|
else if (data[i] == ESC_ESC)
|
||||||
|
{
|
||||||
|
buffer.Add(ESC);
|
||||||
|
}
|
||||||
|
escapeNext = false;
|
||||||
|
}
|
||||||
|
else if (data[i] == END)
|
||||||
|
{
|
||||||
|
if(buffer.Count > 0)
|
||||||
|
{
|
||||||
|
messages.Add(buffer.ToArray());
|
||||||
|
}
|
||||||
|
buffer.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer.Add(data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] Encode(byte[] data)
|
||||||
|
{
|
||||||
|
List<byte> slipData = new List<byte>();
|
||||||
|
|
||||||
|
byte[] esc_end = { ESC, ESC_END };
|
||||||
|
byte[] esc_esc = { ESC, ESC_ESC };
|
||||||
|
byte[] end = { END };
|
||||||
|
|
||||||
|
int length = data.Length;
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
if (data[i] == END)
|
||||||
|
{
|
||||||
|
slipData.AddRange(esc_end);
|
||||||
|
}
|
||||||
|
else if (data[i] == ESC)
|
||||||
|
{
|
||||||
|
slipData.AddRange(esc_esc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slipData.Add(data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slipData.AddRange(end);
|
||||||
|
return slipData.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class Symbol
|
||||||
|
{
|
||||||
|
public string Value;
|
||||||
|
|
||||||
|
public Symbol()
|
||||||
|
{
|
||||||
|
Value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Symbol(string value)
|
||||||
|
{
|
||||||
|
this.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
override
|
||||||
|
public string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(System.Object obj)
|
||||||
|
{
|
||||||
|
if (obj.GetType() == typeof(Symbol))
|
||||||
|
{
|
||||||
|
if (this.Value == ((Symbol)obj).Value)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (obj.GetType() == typeof(string))
|
||||||
|
{
|
||||||
|
if (this.Value == ((string)obj))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Symbol a, Symbol b)
|
||||||
|
{
|
||||||
|
if (a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Symbol a, Symbol b)
|
||||||
|
{
|
||||||
|
if (!a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Value.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Serilog;
|
||||||
|
using TcpSharp;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class MessageEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public OscMessage Message
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TCPClient
|
||||||
|
{
|
||||||
|
private ILogger _log = Log.Logger.ForContext<TCPClient>();
|
||||||
|
|
||||||
|
public int Port
|
||||||
|
{
|
||||||
|
get { return _port; }
|
||||||
|
}
|
||||||
|
int _port;
|
||||||
|
|
||||||
|
public string Address
|
||||||
|
{
|
||||||
|
get { return _address; }
|
||||||
|
}
|
||||||
|
public delegate void MessageReceivedHandler(object source, MessageEventArgs args);
|
||||||
|
public event MessageReceivedHandler? MessageReceived;
|
||||||
|
|
||||||
|
string _address;
|
||||||
|
|
||||||
|
TcpSharpSocketClient? tcpClient;
|
||||||
|
|
||||||
|
public TCPClient(string address, int port)
|
||||||
|
{
|
||||||
|
_port = port;
|
||||||
|
_address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Connect()
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tcpClient = new TcpSharpSocketClient(_address, _port);
|
||||||
|
tcpClient.OnConnected += ClientConnected;
|
||||||
|
tcpClient.OnDataReceived += DataReceived;
|
||||||
|
tcpClient.OnDisconnected += ClientDisconneted;
|
||||||
|
tcpClient.OnError += ClientError;
|
||||||
|
tcpClient.Connect();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Error(e.StackTrace);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
private void ClientError(object sender, OnClientErrorEventArgs e)
|
||||||
|
{
|
||||||
|
_log.Error("client error");
|
||||||
|
_log.Error(e.Exception.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClientDisconneted(object sender, OnClientDisconnectedEventArgs e)
|
||||||
|
{
|
||||||
|
_log.Information($"client disconnected: {e.Reason}");
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DataReceived(object sender, OnClientDataReceivedEventArgs e)
|
||||||
|
{
|
||||||
|
List<byte[]> messages = SlipFrame.Decode(e.Data);
|
||||||
|
_log.Debug("tcpclient: " + messages.Count + " messages pulled from slip");
|
||||||
|
foreach (var message in messages)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
OscMessage responseMessage = OscPacket.GetMessage(message);
|
||||||
|
if (responseMessage == null)
|
||||||
|
{
|
||||||
|
_log.Error("responseMessage is null");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
OnMessageReceived(responseMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Error($"Exception parsing OSC message: {ex.ToString()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClientConnected(object sender, OnClientConnectedEventArgs e)
|
||||||
|
{
|
||||||
|
_log.Debug($"connected to <{_address}:{_port}>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(byte[] message)
|
||||||
|
{
|
||||||
|
byte[] slipData = SlipFrame.Encode(message);
|
||||||
|
tcpClient.SendBytes(slipData.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(OscPacket packet)
|
||||||
|
{
|
||||||
|
byte[] data = packet.GetBytes();
|
||||||
|
Send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (tcpClient == null)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return tcpClient.Connected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
if (tcpClient != null)
|
||||||
|
{
|
||||||
|
tcpClient.OnConnected -= ClientConnected;
|
||||||
|
tcpClient.OnDataReceived -= DataReceived;
|
||||||
|
tcpClient.OnDisconnected -= ClientDisconneted;
|
||||||
|
if (tcpClient.Connected)
|
||||||
|
{
|
||||||
|
_log.Debug($"closing connection to {_address}");
|
||||||
|
tcpClient.Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnMessageReceived(OscMessage msg)
|
||||||
|
{
|
||||||
|
MessageReceived?.Invoke(this, new MessageEventArgs() { Message = msg });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public struct Timetag
|
||||||
|
{
|
||||||
|
public UInt64 Tag;
|
||||||
|
|
||||||
|
public DateTime Timestamp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Utils.TimetagToDateTime(Tag);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Tag = Utils.DateTimeToTimetag(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the fraction of a second in the timestamp. the double precision number is multiplied by 2^32
|
||||||
|
/// giving an accuracy down to about 230 picoseconds ( 1/(2^32) of a second)
|
||||||
|
/// </summary>
|
||||||
|
public double Fraction
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Utils.TimetagToFraction(Tag);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Tag = (Tag & 0xFFFFFFFF00000000) + (UInt32)(value * 0xFFFFFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Timetag(UInt64 value)
|
||||||
|
{
|
||||||
|
this.Tag = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Timetag(DateTime value)
|
||||||
|
{
|
||||||
|
Tag = 0;
|
||||||
|
this.Timestamp = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(System.Object obj)
|
||||||
|
{
|
||||||
|
if (obj.GetType() == typeof(Timetag))
|
||||||
|
{
|
||||||
|
if (this.Tag == ((Timetag)obj).Tag)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (obj.GetType() == typeof(UInt64))
|
||||||
|
{
|
||||||
|
if (this.Tag == ((UInt64)obj))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Timetag a, Timetag b)
|
||||||
|
{
|
||||||
|
if (a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Timetag a, Timetag b)
|
||||||
|
{
|
||||||
|
if (a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return (int)( ((uint)(Tag >> 32) + (uint)(Tag & 0x00000000FFFFFFFF)) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public delegate void HandleOscPacket(OscPacket packet);
|
||||||
|
public delegate void HandleBytePacket(byte[] packet);
|
||||||
|
|
||||||
|
public class UDPListener : IDisposable
|
||||||
|
{
|
||||||
|
public int Port { get; private set; }
|
||||||
|
|
||||||
|
object callbackLock;
|
||||||
|
|
||||||
|
UdpClient receivingUdpClient;
|
||||||
|
IPEndPoint RemoteIpEndPoint;
|
||||||
|
|
||||||
|
HandleBytePacket BytePacketCallback = null;
|
||||||
|
HandleOscPacket OscPacketCallback = null;
|
||||||
|
|
||||||
|
Queue<byte[]> queue;
|
||||||
|
ManualResetEvent ClosingEvent;
|
||||||
|
|
||||||
|
public UDPListener(int port)
|
||||||
|
{
|
||||||
|
Port = port;
|
||||||
|
queue = new Queue<byte[]>();
|
||||||
|
ClosingEvent = new ManualResetEvent(false);
|
||||||
|
callbackLock = new object();
|
||||||
|
|
||||||
|
// try to open the port 10 times, else fail
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
receivingUdpClient = new UdpClient(port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Failed in ten tries, throw the exception and give up
|
||||||
|
if (i >= 9)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
Thread.Sleep(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||||
|
|
||||||
|
// setup first async event
|
||||||
|
AsyncCallback callBack = new AsyncCallback(ReceiveCallback);
|
||||||
|
receivingUdpClient.BeginReceive(callBack, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UDPListener(int port, HandleOscPacket callback) : this(port)
|
||||||
|
{
|
||||||
|
this.OscPacketCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UDPListener(int port, HandleBytePacket callback) : this(port)
|
||||||
|
{
|
||||||
|
this.BytePacketCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReceiveCallback(IAsyncResult result)
|
||||||
|
{
|
||||||
|
Monitor.Enter(callbackLock);
|
||||||
|
Byte[] bytes = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bytes = receivingUdpClient.EndReceive(result, ref RemoteIpEndPoint);
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException e)
|
||||||
|
{
|
||||||
|
// Ignore if disposed. This happens when closing the listener
|
||||||
|
Log.Debug(e.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process bytes
|
||||||
|
if (bytes != null && bytes.Length > 0)
|
||||||
|
{
|
||||||
|
if (BytePacketCallback != null)
|
||||||
|
{
|
||||||
|
BytePacketCallback(bytes);
|
||||||
|
}
|
||||||
|
else if (OscPacketCallback != null)
|
||||||
|
{
|
||||||
|
OscPacket packet = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
packet = OscPacket.GetPacket(bytes);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Debug(e.ToString());
|
||||||
|
// If there is an error reading the packet, null is sent to the callback
|
||||||
|
}
|
||||||
|
//Log.Debug("Raw UDP In: " + System.Text.Encoding.ASCII.GetString(bytes));
|
||||||
|
OscPacketCallback(packet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lock (queue)
|
||||||
|
{
|
||||||
|
queue.Enqueue(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closing)
|
||||||
|
ClosingEvent.Set();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Setup next async event
|
||||||
|
AsyncCallback callBack = new AsyncCallback(ReceiveCallback);
|
||||||
|
receivingUdpClient.BeginReceive(callBack, null);
|
||||||
|
}
|
||||||
|
Monitor.Exit(callbackLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool closing = false;
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
lock (callbackLock)
|
||||||
|
{
|
||||||
|
ClosingEvent.Reset();
|
||||||
|
closing = true;
|
||||||
|
receivingUdpClient.Close();
|
||||||
|
}
|
||||||
|
ClosingEvent.WaitOne();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public OscPacket Receive()
|
||||||
|
{
|
||||||
|
if (closing) throw new Exception("UDPListener has been closed.");
|
||||||
|
|
||||||
|
lock (queue)
|
||||||
|
{
|
||||||
|
if (queue.Count() > 0)
|
||||||
|
{
|
||||||
|
byte[] bytes = queue.Dequeue();
|
||||||
|
var packet = OscPacket.GetPacket(bytes);
|
||||||
|
//Log.Debug("Raw UDP In: " + System.Text.Encoding.ASCII.GetString(bytes));
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ReceiveBytes()
|
||||||
|
{
|
||||||
|
if (closing) throw new Exception("UDPListener has been closed.");
|
||||||
|
|
||||||
|
lock (queue)
|
||||||
|
{
|
||||||
|
if (queue.Count() > 0)
|
||||||
|
{
|
||||||
|
byte[] bytes = queue.Dequeue();
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class UDPSender
|
||||||
|
{
|
||||||
|
public int Port
|
||||||
|
{
|
||||||
|
get { return _port; }
|
||||||
|
}
|
||||||
|
int _port;
|
||||||
|
|
||||||
|
public string Address
|
||||||
|
{
|
||||||
|
get { return _address; }
|
||||||
|
}
|
||||||
|
string _address;
|
||||||
|
|
||||||
|
IPEndPoint RemoteIpEndPoint;
|
||||||
|
Socket sock;
|
||||||
|
|
||||||
|
public UDPSender(string address, int port)
|
||||||
|
{
|
||||||
|
_port = port;
|
||||||
|
_address = address;
|
||||||
|
|
||||||
|
sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||||
|
|
||||||
|
var addresses = System.Net.Dns.GetHostAddresses(address);
|
||||||
|
if (addresses.Length == 0) throw new Exception("Unable to find IP address for " + address);
|
||||||
|
|
||||||
|
RemoteIpEndPoint = new IPEndPoint(addresses[0], port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(byte[] message)
|
||||||
|
{
|
||||||
|
sock.SendTo(message, RemoteIpEndPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(OscPacket packet)
|
||||||
|
{
|
||||||
|
byte[] data = packet.GetBytes();
|
||||||
|
Send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
sock.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class Utils
|
||||||
|
{
|
||||||
|
public static DateTime TimetagToDateTime(UInt64 val)
|
||||||
|
{
|
||||||
|
if (val == 1)
|
||||||
|
return DateTime.Now;
|
||||||
|
|
||||||
|
UInt32 seconds = (UInt32)(val >> 32);
|
||||||
|
var time = DateTime.Parse("1900-01-01 00:00:00");
|
||||||
|
time = time.AddSeconds(seconds);
|
||||||
|
var fraction = TimetagToFraction(val);
|
||||||
|
time = time.AddSeconds(fraction);
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double TimetagToFraction(UInt64 val)
|
||||||
|
{
|
||||||
|
if (val == 1)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
UInt32 seconds = (UInt32)(val & 0x00000000FFFFFFFF);
|
||||||
|
double fraction = (double)seconds / (UInt32)(0xFFFFFFFF);
|
||||||
|
return fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UInt64 DateTimeToTimetag(DateTime value)
|
||||||
|
{
|
||||||
|
UInt64 seconds = (UInt32)(value - DateTime.Parse("1900-01-01 00:00:00.000")).TotalSeconds;
|
||||||
|
UInt64 fraction = (UInt32)(0xFFFFFFFF * ((double)value.Millisecond / 1000));
|
||||||
|
|
||||||
|
UInt64 output = (seconds << 32) + fraction;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int AlignedStringLength(string val)
|
||||||
|
{
|
||||||
|
int len = val.Length + (4 - val.Length % 4);
|
||||||
|
if (len <= val.Length) len += 4;
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class ConnectedClient
|
||||||
|
{
|
||||||
|
/* Public Properties */
|
||||||
|
public TcpClient Client { get; internal set; }
|
||||||
|
public string ConnectionId { get; internal set; }
|
||||||
|
public bool Connected { get { return this.Client != null && this.Client.Connected; } }
|
||||||
|
public bool AcceptData { get; internal set; } = true;
|
||||||
|
public long BytesReceived { get; private set; }
|
||||||
|
public long BytesSent { get; private set; }
|
||||||
|
|
||||||
|
/* Reference Fields */
|
||||||
|
private readonly TcpSharpSocketServer _server;
|
||||||
|
|
||||||
|
/* Private Fields */
|
||||||
|
private readonly CancellationTokenSource _cancellationTokenSource;
|
||||||
|
private readonly CancellationToken _cancellationToken;
|
||||||
|
|
||||||
|
internal ConnectedClient(TcpSharpSocketServer server, TcpClient client, string connectionId)
|
||||||
|
{
|
||||||
|
this.Client = client;
|
||||||
|
this.ConnectionId = connectionId;
|
||||||
|
|
||||||
|
this._server = server;
|
||||||
|
this._cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
this._cancellationToken = this._cancellationTokenSource.Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void StartReceiving()
|
||||||
|
{
|
||||||
|
Task.Factory.StartNew(ReceivingTask, TaskCreationOptions.LongRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void StopReceiving()
|
||||||
|
{
|
||||||
|
this._cancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReceivingTask()
|
||||||
|
{
|
||||||
|
var stream = this.Client.GetStream();
|
||||||
|
var buffer = new byte[this.Client.ReceiveBufferSize];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytesCount = 0;
|
||||||
|
while (!this._cancellationToken.IsCancellationRequested && (bytesCount = await stream.ReadAsync(buffer, 0, buffer.Length, this._cancellationToken)) != 0)
|
||||||
|
{
|
||||||
|
// Increase BytesReceived
|
||||||
|
BytesReceived += bytesCount;
|
||||||
|
this._server.AddReceivedBytes(bytesCount);
|
||||||
|
|
||||||
|
// Invoke OnDataReceived
|
||||||
|
if (this.AcceptData)
|
||||||
|
{
|
||||||
|
var bytesReceived = new byte[bytesCount];
|
||||||
|
Array.Copy(buffer, bytesReceived, bytesCount);
|
||||||
|
this._server.InvokeOnDataReceived(new OnServerDataReceivedEventArgs
|
||||||
|
{
|
||||||
|
Client = this.Client,
|
||||||
|
ConnectionId = this.ConnectionId,
|
||||||
|
Data = bytesReceived
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
// Disconnect
|
||||||
|
this._server.Disconnect(this.ConnectionId, DisconnectReason.Exception);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Invoke OnError
|
||||||
|
this._server.InvokeOnError(new OnServerErrorEventArgs
|
||||||
|
{
|
||||||
|
Client = this.Client,
|
||||||
|
ConnectionId = this.ConnectionId,
|
||||||
|
Exception = ex
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disconnect
|
||||||
|
this._server.Disconnect(this.ConnectionId, DisconnectReason.Exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendBytes(byte[] bytes)
|
||||||
|
{
|
||||||
|
if (!this.Connected) return 0;
|
||||||
|
|
||||||
|
this.BytesSent += bytes.Length;
|
||||||
|
this._server.AddSentBytes(bytes.Length);
|
||||||
|
|
||||||
|
return this.Client.Client.Send(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendString(string data)
|
||||||
|
{
|
||||||
|
if (!this.Connected) return 0;
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(data);
|
||||||
|
this.BytesSent += bytes.Length;
|
||||||
|
this._server.AddSentBytes(bytes.Length);
|
||||||
|
|
||||||
|
return this.Client.Client.Send(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendString(string data, Encoding encoding)
|
||||||
|
{
|
||||||
|
if (!this.Connected) return 0;
|
||||||
|
|
||||||
|
var bytes = encoding.GetBytes(data);
|
||||||
|
this.BytesSent += bytes.Length;
|
||||||
|
this._server.AddSentBytes(bytes.Length);
|
||||||
|
|
||||||
|
return this.Client.Client.Send(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendFile(string filePath)
|
||||||
|
{
|
||||||
|
// Check Point
|
||||||
|
if (!this.Connected) return 0;
|
||||||
|
if (!File.Exists(filePath)) return 0;
|
||||||
|
|
||||||
|
// FileInfo
|
||||||
|
var fileInfo = new FileInfo(filePath);
|
||||||
|
if (fileInfo == null) return 0;
|
||||||
|
|
||||||
|
// Action
|
||||||
|
this.Client.Client.SendFile(filePath);
|
||||||
|
this.BytesSent += fileInfo.Length;
|
||||||
|
this._server.AddSentBytes(fileInfo.Length);
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return fileInfo.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendFile(string filePath, byte[] preBuffer, byte[] postBuffer, TransmitFileOptions flags)
|
||||||
|
{
|
||||||
|
// Check Point
|
||||||
|
if (!this.Connected) return 0;
|
||||||
|
if (!File.Exists(filePath)) return 0;
|
||||||
|
|
||||||
|
// FileInfo
|
||||||
|
var fileInfo = new FileInfo(filePath);
|
||||||
|
if (fileInfo == null) return 0;
|
||||||
|
|
||||||
|
// Action
|
||||||
|
this.Client.Client.SendFile(filePath, preBuffer, postBuffer, flags);
|
||||||
|
this.BytesSent += fileInfo.Length;
|
||||||
|
this._server.AddSentBytes(fileInfo.Length);
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return fileInfo.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public enum DisconnectReason : byte
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Exception = 1,
|
||||||
|
ServerAborted = 2,
|
||||||
|
ServerStopped = 3,
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnClientConnectedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public IPAddress ServerIPAddress
|
||||||
|
{
|
||||||
|
get { return IPAddress.Parse(ServerHost); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ServerHost { get; internal set; }
|
||||||
|
|
||||||
|
public int ServerPort { get; internal set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnClientDataReceivedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public byte[] Data { get; internal set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnClientDisconnectedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public DisconnectReason Reason { get; internal set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnClientErrorEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public Exception Exception { get; internal set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnClientReconnectedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public IPAddress ServerIPAddress => IPAddress.Parse(ServerHost);
|
||||||
|
public string ServerHost { get; internal set; }
|
||||||
|
public int ServerPort { get; internal set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnServerConnectedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public IPEndPoint IPEndPoint { get; internal set; }
|
||||||
|
public string IPAddress { get; internal set; }
|
||||||
|
public int Port { get; internal set; }
|
||||||
|
public string ConnectionId { get; internal set; }
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnServerConnectionRequestEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public IPEndPoint IPEndPoint { get; internal set; }
|
||||||
|
public string IPAddress { get; internal set; }
|
||||||
|
public int Port { get; internal set; }
|
||||||
|
public bool Accept { get; set; } = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnServerDataReceivedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public TcpClient Client { get; internal set; }
|
||||||
|
public string ConnectionId { get; internal set; }
|
||||||
|
public byte[] Data { get; internal set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnServerDisconnectedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public string ConnectionId { get; internal set; }
|
||||||
|
public DisconnectReason Reason { get; internal set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnServerErrorEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public TcpClient Client { get; internal set; }
|
||||||
|
public string ConnectionId { get; internal set; }
|
||||||
|
public Exception Exception { get; internal set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnServerStartedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public bool IsStarted { get; internal set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class OnServerStoppedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public bool IsStopped { get; internal set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generated id is composed of
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item><description>time - 41 bits (millisecond precision w/ a custom epoch gives us 69 years)</description></item>
|
||||||
|
/// <item><description>configured machine id - 10 bits (5 bit worker id, 5 bits datacenter id) - gives us up to 1024 machines</description></item>
|
||||||
|
/// <item><description>sequence number - 12 bits - rolls over every 4096 per machine (with protection to avoid rollover in the same ms)</description></item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
internal class SnowflakeGenerator : IEnumerable<long>
|
||||||
|
{
|
||||||
|
#region Private Constant
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1 January 1970. Used to calculate timestamp (in milliseconds)
|
||||||
|
/// </summary>
|
||||||
|
private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
private const long Epoch = 1668124800000L;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of bits allocated for a worker id in the generated identifier. 5 bits indicates values from 0 to 31
|
||||||
|
/// </summary>
|
||||||
|
private const int WorkerIdBits = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Datacenter identifier this worker belongs to. 5 bits indicates values from 0 to 31
|
||||||
|
/// </summary>
|
||||||
|
private const int DatacenterIdBits = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generator identifier. 10 bits indicates values from 0 to 1023
|
||||||
|
/// </summary>
|
||||||
|
private const int GeneratorIdBits = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum generator identifier
|
||||||
|
/// </summary>
|
||||||
|
private const long MaxGeneratorId = -1 ^ -1L << GeneratorIdBits;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum worker identifier
|
||||||
|
/// </summary>
|
||||||
|
private const long MaxWorkerId = -1L ^ -1L << WorkerIdBits;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum datacenter identifier
|
||||||
|
/// </summary>
|
||||||
|
private const long MaxDatacenterId = -1L ^ -1L << DatacenterIdBits;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of bits allocated for sequence in the generated identifier
|
||||||
|
/// </summary>
|
||||||
|
private const int SequenceBits = 12;
|
||||||
|
|
||||||
|
private const int WorkerIdShift = SequenceBits;
|
||||||
|
|
||||||
|
private const int DatacenterIdShift = SequenceBits + WorkerIdBits;
|
||||||
|
|
||||||
|
private const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits;
|
||||||
|
|
||||||
|
private const long SequenceMask = -1L ^ -1L << SequenceBits;
|
||||||
|
|
||||||
|
#endregion Private Constant
|
||||||
|
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Object used as a monitor for threads synchronization.
|
||||||
|
/// </summary>
|
||||||
|
private readonly object monitor = new object();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The timestamp used to generate last id by the worker
|
||||||
|
/// </summary>
|
||||||
|
private long lastTimestamp = -1L;
|
||||||
|
|
||||||
|
private long sequence = 0;
|
||||||
|
|
||||||
|
#endregion Private Fields
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates how many times the given generator had to wait
|
||||||
|
/// for next millisecond <see cref="TillNextMillis"/> since startup.
|
||||||
|
/// </summary>
|
||||||
|
public int NextMillisecondWait { get; set; }
|
||||||
|
|
||||||
|
#endregion Public Properties
|
||||||
|
|
||||||
|
#region Public Constructors
|
||||||
|
|
||||||
|
public SnowflakeGenerator(int generatorId = 0, int sequence = 0) : this((int)(generatorId & MaxWorkerId), (int)(generatorId >> WorkerIdBits & MaxDatacenterId), sequence)
|
||||||
|
{
|
||||||
|
// sanity check for generatorId
|
||||||
|
if (generatorId > MaxGeneratorId || generatorId < 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
string.Format("Generator Id can't be greater than {0} or less than 0", MaxGeneratorId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SnowflakeGenerator(int workerId, int datacenterId, int sequence = 0)
|
||||||
|
{
|
||||||
|
// sanity check for workerId
|
||||||
|
if (workerId > MaxWorkerId || workerId < 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
string.Format("Worker Id can't be greater than {0} or less than 0", MaxWorkerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanity check for datacenterId
|
||||||
|
if (datacenterId > MaxDatacenterId || datacenterId < 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
string.Format("Datacenter Id can't be greater than {0} or less than 0", MaxDatacenterId));
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkerId = workerId;
|
||||||
|
DatacenterId = datacenterId;
|
||||||
|
this.sequence = sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public Constructors
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The identifier of the worker
|
||||||
|
/// </summary>
|
||||||
|
public long WorkerId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifier of datacenter the worker belongs to
|
||||||
|
/// </summary>
|
||||||
|
public long DatacenterId { get; private set; }
|
||||||
|
|
||||||
|
#endregion Public Properties
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
public long GenerateId()
|
||||||
|
{
|
||||||
|
lock (monitor)
|
||||||
|
{
|
||||||
|
return NextId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<long> GetEnumerator()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
yield return GenerateId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion Public Methods
|
||||||
|
|
||||||
|
#region Private Properties
|
||||||
|
|
||||||
|
private static long CurrentTime
|
||||||
|
{
|
||||||
|
get { return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Private Properties
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
#endregion Public Methods
|
||||||
|
|
||||||
|
#region Private Static Methods
|
||||||
|
#endregion Private Static Methods
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private long TillNextMillis(long lastTimestamp)
|
||||||
|
{
|
||||||
|
NextMillisecondWait++;
|
||||||
|
|
||||||
|
var timestamp = CurrentTime;
|
||||||
|
|
||||||
|
SpinWait.SpinUntil(() => (timestamp = CurrentTime) > lastTimestamp);
|
||||||
|
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long NextId()
|
||||||
|
{
|
||||||
|
var timestamp = CurrentTime;
|
||||||
|
|
||||||
|
if (timestamp < lastTimestamp)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Format("Clock moved backwards. Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastTimestamp == timestamp)
|
||||||
|
{
|
||||||
|
sequence = sequence + 1 & SequenceMask;
|
||||||
|
if (sequence == 0)
|
||||||
|
{
|
||||||
|
timestamp = TillNextMillis(lastTimestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sequence = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTimestamp = timestamp;
|
||||||
|
return timestamp - Epoch << TimestampLeftShift |
|
||||||
|
DatacenterId << DatacenterIdShift |
|
||||||
|
WorkerId << WorkerIdShift |
|
||||||
|
sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Private Methods
|
||||||
|
}
|
||||||
@@ -0,0 +1,519 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class TcpSharpSocketClient
|
||||||
|
{
|
||||||
|
#region Public Properties
|
||||||
|
public string Host
|
||||||
|
{
|
||||||
|
get { return _host; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Connected)
|
||||||
|
throw (new Exception("Socket Client is already connected. You cant change this property while connected."));
|
||||||
|
|
||||||
|
_host = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int Port
|
||||||
|
{
|
||||||
|
get { return _port; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Connected)
|
||||||
|
throw (new Exception("Socket Client is already connected. You cant change this property while connected."));
|
||||||
|
|
||||||
|
_port = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool NoDelay
|
||||||
|
{
|
||||||
|
get { return _nodelay; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_nodelay = value;
|
||||||
|
if (_socket != null) _socket.NoDelay = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool KeepAlive
|
||||||
|
{
|
||||||
|
get { return _keepAlive; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Connected)
|
||||||
|
throw (new Exception("Socket Client is already connected. You cant change this property while connected."));
|
||||||
|
|
||||||
|
_keepAlive = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int KeepAliveTime
|
||||||
|
{
|
||||||
|
get { return _keepAliveTime; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Connected)
|
||||||
|
throw (new Exception("Socket Client is already connected. You cant change this property while connected."));
|
||||||
|
|
||||||
|
_keepAliveTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keep-alive interval in seconds
|
||||||
|
/// </summary>
|
||||||
|
public int KeepAliveInterval
|
||||||
|
{
|
||||||
|
get { return _keepAliveInterval; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Connected)
|
||||||
|
throw (new Exception("Socket Client is already connected. You cant change this property while connected."));
|
||||||
|
|
||||||
|
_keepAliveInterval = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int KeepAliveRetryCount
|
||||||
|
{
|
||||||
|
get { return _keepAliveRetryCount; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Connected)
|
||||||
|
throw (new Exception("Socket Client is already connected. You cant change this property while connected."));
|
||||||
|
|
||||||
|
_keepAliveRetryCount = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int ReceiveBufferSize
|
||||||
|
{
|
||||||
|
get { return _receiveBufferSize; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_receiveBufferSize = value;
|
||||||
|
_recvBuffer = new byte[value];
|
||||||
|
if (_socket != null) _socket.ReceiveBufferSize = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int ReceiveTimeout
|
||||||
|
{
|
||||||
|
get { return _receiveTimeout; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_receiveTimeout = value;
|
||||||
|
if (_socket != null) _socket.ReceiveTimeout = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int SendBufferSize
|
||||||
|
{
|
||||||
|
get { return _sendBufferSize; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_sendBufferSize = value;
|
||||||
|
_sendBuffer = new byte[value];
|
||||||
|
if (_socket != null) _socket.SendBufferSize = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int SendTimeout
|
||||||
|
{
|
||||||
|
get { return _sendTimeout; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_sendTimeout = value;
|
||||||
|
if (_socket != null) _socket.SendTimeout = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public long BytesReceived
|
||||||
|
{
|
||||||
|
get { return _bytesReceived; }
|
||||||
|
internal set { _bytesReceived = value; }
|
||||||
|
}
|
||||||
|
public long BytesSent
|
||||||
|
{
|
||||||
|
get { return _bytesSent; }
|
||||||
|
internal set { _bytesSent = value; }
|
||||||
|
}
|
||||||
|
public bool Reconnect
|
||||||
|
{
|
||||||
|
get { return _reconnect; }
|
||||||
|
set { _reconnect = value; }
|
||||||
|
}
|
||||||
|
public bool Reconnecting { get; private set; }
|
||||||
|
public int ReconnectDelayInSeconds
|
||||||
|
{
|
||||||
|
get { return _reconnectDelay; }
|
||||||
|
set { _reconnectDelay = value; }
|
||||||
|
}
|
||||||
|
public bool AcceptData
|
||||||
|
{
|
||||||
|
get { return _acceptData; }
|
||||||
|
set { _acceptData = value; }
|
||||||
|
}
|
||||||
|
public bool Connected { get { return this._socket != null && this._socket.Connected; } }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Properties
|
||||||
|
private string _host;
|
||||||
|
private int _port;
|
||||||
|
private bool _nodelay = true;
|
||||||
|
private bool _keepAlive = false;
|
||||||
|
private int _keepAliveTime = 900;
|
||||||
|
private int _keepAliveInterval = 300;
|
||||||
|
private int _keepAliveRetryCount = 5;
|
||||||
|
private int _receiveBufferSize = 8192;
|
||||||
|
private int _receiveTimeout = 0;
|
||||||
|
private int _sendBufferSize = 8192;
|
||||||
|
private int _sendTimeout = 0;
|
||||||
|
private long _bytesReceived;
|
||||||
|
private long _bytesSent;
|
||||||
|
private bool _reconnect = false;
|
||||||
|
private int _reconnectDelay = 5;
|
||||||
|
private bool _acceptData = true;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Events
|
||||||
|
public event EventHandler<OnClientErrorEventArgs> OnError = delegate { };
|
||||||
|
public event EventHandler<OnClientConnectedEventArgs> OnConnected = delegate { };
|
||||||
|
public event EventHandler<OnClientReconnectedEventArgs> OnReconnected = delegate { };
|
||||||
|
public event EventHandler<OnClientDisconnectedEventArgs> OnDisconnected = delegate { };
|
||||||
|
public event EventHandler<OnClientDataReceivedEventArgs> OnDataReceived = delegate { };
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Invokers
|
||||||
|
internal void InvokeOnError(OnClientErrorEventArgs args) => this.OnError?.Invoke(this, args);
|
||||||
|
internal void InvokeOnConnected(OnClientConnectedEventArgs args) => this.OnConnected?.Invoke(this, args);
|
||||||
|
internal void InvokeOnReconnected(OnClientReconnectedEventArgs args) => this.OnReconnected?.Invoke(this, args);
|
||||||
|
internal void InvokeOnDisconnected(OnClientDisconnectedEventArgs args) => this.OnDisconnected?.Invoke(this, args);
|
||||||
|
internal void InvokeOnDataReceived(OnClientDataReceivedEventArgs args) => this.OnDataReceived?.Invoke(this, args);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Fields
|
||||||
|
private Socket _socket;
|
||||||
|
private byte[] _recvBuffer;
|
||||||
|
private byte[] _sendBuffer;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Receiver Task
|
||||||
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
|
private CancellationToken _cancellationToken;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
public TcpSharpSocketClient() : this("127.0.0.1", 1024)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TcpSharpSocketClient(string host, int port)
|
||||||
|
{
|
||||||
|
this.Host = host;
|
||||||
|
this.Port = port;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
public void Connect()
|
||||||
|
{
|
||||||
|
// Buffers
|
||||||
|
this._recvBuffer = new byte[ReceiveBufferSize];
|
||||||
|
this._sendBuffer = new byte[SendBufferSize];
|
||||||
|
|
||||||
|
// Get Host IP Address that is used to establish a connection
|
||||||
|
// In this case, we get one IP address of localhost that is IP : 127.0.0.1
|
||||||
|
// If a host has multiple addresses, you will get a list of addresses
|
||||||
|
IPAddress serverIPAddress;
|
||||||
|
if (!IPAddress.TryParse(Host, out serverIPAddress))
|
||||||
|
{
|
||||||
|
var serverIPHost = Dns.GetHostEntry(Host);
|
||||||
|
if (serverIPHost.AddressList.Length == 0) throw new Exception("Unable to solve host address");
|
||||||
|
serverIPAddress = serverIPHost.AddressList[0];
|
||||||
|
if (serverIPAddress.ToString() == "::1") serverIPAddress = new IPAddress(16777343); // 127.0.0.1
|
||||||
|
}
|
||||||
|
var serverIPEndPoint = new IPEndPoint(serverIPAddress, Port);
|
||||||
|
|
||||||
|
// Create a TCP/IP socket.
|
||||||
|
this._socket = new Socket(serverIPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
|
||||||
|
// Set Properties
|
||||||
|
this._socket.NoDelay = this.NoDelay;
|
||||||
|
this._socket.ReceiveBufferSize = this.ReceiveBufferSize;
|
||||||
|
this._socket.ReceiveTimeout = this.ReceiveTimeout;
|
||||||
|
this._socket.SendBufferSize = this.SendBufferSize;
|
||||||
|
this._socket.SendTimeout = this.SendTimeout;
|
||||||
|
|
||||||
|
/* Keep Alive */
|
||||||
|
if (this.KeepAlive && this.KeepAliveInterval > 0)
|
||||||
|
{
|
||||||
|
#if NETCOREAPP3_0_OR_GREATER || NET5_0_OR_GREATER
|
||||||
|
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
|
||||||
|
_socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, this.KeepAliveTime);
|
||||||
|
_socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, this.KeepAliveInterval);
|
||||||
|
_socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, this.KeepAliveRetryCount);
|
||||||
|
#elif NETFRAMEWORK
|
||||||
|
// Get the size of the uint to use to back the byte array
|
||||||
|
// int size = Marshal.SizeOf((uint)0);
|
||||||
|
int size = sizeof(uint);
|
||||||
|
|
||||||
|
// Create the byte array
|
||||||
|
byte[] keepAlive = new byte[size * 3];
|
||||||
|
|
||||||
|
// Pack the byte array:
|
||||||
|
// Turn keepalive on
|
||||||
|
Buffer.BlockCopy(BitConverter.GetBytes((uint)1), 0, keepAlive, 0, size);
|
||||||
|
|
||||||
|
// How long does it take to start the first probe (in milliseconds)
|
||||||
|
Buffer.BlockCopy(BitConverter.GetBytes((uint)(KeepAliveTime * 1000)), 0, keepAlive, size, size);
|
||||||
|
|
||||||
|
// Detection time interval (in milliseconds)
|
||||||
|
Buffer.BlockCopy(BitConverter.GetBytes((uint)(KeepAliveInterval * 1000)), 0, keepAlive, size * 2, size);
|
||||||
|
|
||||||
|
// Set the keep-alive settings on the underlying Socket
|
||||||
|
_socket.IOControl(IOControlCode.KeepAliveValues, keepAlive, null);
|
||||||
|
#elif NETSTANDARD
|
||||||
|
// Set the keep-alive settings on the underlying Socket
|
||||||
|
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to Remote EndPoint
|
||||||
|
_socket.Connect(serverIPEndPoint);
|
||||||
|
|
||||||
|
// Start Receiver Thread
|
||||||
|
if (this._cancellationTokenSource != null) this._cancellationTokenSource.Cancel();
|
||||||
|
this._cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
this._cancellationToken = this._cancellationTokenSource.Token;
|
||||||
|
Task.Factory.StartNew(ReceiverTask, TaskCreationOptions.LongRunning);
|
||||||
|
|
||||||
|
// Invoke OnConnected
|
||||||
|
this.InvokeOnConnected(new OnClientConnectedEventArgs
|
||||||
|
{
|
||||||
|
ServerHost = this.Host,
|
||||||
|
ServerPort = this.Port,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
this.Disconnect(DisconnectReason.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendBytes(byte[] bytes)
|
||||||
|
{
|
||||||
|
// Check Point
|
||||||
|
if (!this.Connected) return 0;
|
||||||
|
|
||||||
|
// Action
|
||||||
|
var sent = this._socket.Send(bytes);
|
||||||
|
this.BytesSent += sent;
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> SendBytesAsync(byte[] bytes, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return SendBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendString(string data)
|
||||||
|
{
|
||||||
|
// Check Point
|
||||||
|
if (!this.Connected) return 0;
|
||||||
|
|
||||||
|
// Action
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(data);
|
||||||
|
var sent = this._socket.Send(bytes);
|
||||||
|
this.BytesSent += sent;
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> SendStringAsync(string data, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return SendString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendString(string data, Encoding encoding)
|
||||||
|
{
|
||||||
|
// Check Point
|
||||||
|
if (!this.Connected) return 0;
|
||||||
|
|
||||||
|
// Action
|
||||||
|
var bytes = encoding.GetBytes(data);
|
||||||
|
var sent = this._socket.Send(bytes);
|
||||||
|
this.BytesSent += sent;
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> SendStringAsync(string data, Encoding encoding, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return SendString(data, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendFile(string fileName)
|
||||||
|
{
|
||||||
|
// Check Point
|
||||||
|
if (!this.Connected) return 0;
|
||||||
|
if (!File.Exists(fileName)) return 0;
|
||||||
|
|
||||||
|
// FileInfo
|
||||||
|
var fileInfo = new FileInfo(fileName);
|
||||||
|
if (fileInfo == null) return 0;
|
||||||
|
|
||||||
|
// Action
|
||||||
|
this._socket.SendFile(fileName);
|
||||||
|
this.BytesSent += fileInfo.Length;
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return fileInfo.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> SendFileAsync(string fileName, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return SendFile(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendFile(string fileName, byte[] preBuffer, byte[] postBuffer, TransmitFileOptions flags)
|
||||||
|
{
|
||||||
|
// Check Point
|
||||||
|
if (!this.Connected) return 0;
|
||||||
|
if (!File.Exists(fileName)) return 0;
|
||||||
|
|
||||||
|
// FileInfo
|
||||||
|
var fileInfo = new FileInfo(fileName);
|
||||||
|
if (fileInfo == null) return 0;
|
||||||
|
|
||||||
|
// Action
|
||||||
|
this._socket.SendFile(fileName, preBuffer, postBuffer, flags);
|
||||||
|
this.BytesSent += fileInfo.Length;
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return fileInfo.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> SendFileAsync(string fileName, byte[] preBuffer, byte[] postBuffer, TransmitFileOptions flags, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return SendFile(fileName, preBuffer, postBuffer, flags);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
private void ReceiverTask()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!_cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Receive the response from the remote device.
|
||||||
|
var bytesCount = _socket.Receive(_recvBuffer);
|
||||||
|
if (bytesCount > 0)
|
||||||
|
{
|
||||||
|
BytesReceived += bytesCount;
|
||||||
|
if (this.AcceptData)
|
||||||
|
{
|
||||||
|
var bytes = new byte[bytesCount];
|
||||||
|
Array.Copy(_recvBuffer, bytes, bytesCount);
|
||||||
|
|
||||||
|
// Invoke OnDataReceived
|
||||||
|
this.InvokeOnDataReceived(new OnClientDataReceivedEventArgs
|
||||||
|
{
|
||||||
|
Data = bytes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException ex)
|
||||||
|
{
|
||||||
|
if (ex.SocketErrorCode == SocketError.ConnectionAborted)
|
||||||
|
{
|
||||||
|
Disconnect(DisconnectReason.ServerAborted);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Disconnect(DisconnectReason.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Invoke OnError
|
||||||
|
this.InvokeOnError(new OnClientErrorEventArgs
|
||||||
|
{
|
||||||
|
Exception = ex
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disconnect
|
||||||
|
Disconnect(DisconnectReason.Exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Disconnect(DisconnectReason reason)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Stop Receiver Task
|
||||||
|
this._cancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Release the socket.
|
||||||
|
_socket.Shutdown(SocketShutdown.Both);
|
||||||
|
_socket.Close();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Dispose
|
||||||
|
_socket.Dispose();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// Invoke OnDisconnected
|
||||||
|
this.InvokeOnDisconnected(new OnClientDisconnectedEventArgs());
|
||||||
|
|
||||||
|
// Reconnect
|
||||||
|
if (this.Reconnect && !Reconnecting)
|
||||||
|
{
|
||||||
|
Reconnecting = true;
|
||||||
|
while (!this.Connected)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Task.Delay(this.ReconnectDelayInSeconds * 1000);
|
||||||
|
this.Connect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
Reconnecting = false;
|
||||||
|
|
||||||
|
// Invoke OnReconnected
|
||||||
|
this.InvokeOnReconnected(new OnClientReconnectedEventArgs
|
||||||
|
{
|
||||||
|
ServerHost = this.Host,
|
||||||
|
ServerPort = this.Port,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,463 @@
|
|||||||
|
namespace TcpSharp;
|
||||||
|
|
||||||
|
public class TcpSharpSocketServer
|
||||||
|
{
|
||||||
|
#region Public Properties
|
||||||
|
public bool Listening
|
||||||
|
{
|
||||||
|
get { return _isListening; }
|
||||||
|
private set { _isListening = value; }
|
||||||
|
}
|
||||||
|
public int Port
|
||||||
|
{
|
||||||
|
get { return _port; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Listening)
|
||||||
|
throw (new Exception("Socket Server is already listening. You cant change this property while listening."));
|
||||||
|
|
||||||
|
_port = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool NoDelay
|
||||||
|
{
|
||||||
|
get { return _nodelay; }
|
||||||
|
private set { _nodelay = value; }
|
||||||
|
}
|
||||||
|
public bool KeepAlive
|
||||||
|
{
|
||||||
|
get { return _keepAlive; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Listening)
|
||||||
|
throw (new Exception("Socket Server is already listening. You cant change this property while listening."));
|
||||||
|
|
||||||
|
_keepAlive = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int KeepAliveTime
|
||||||
|
{
|
||||||
|
get { return _keepAliveTime; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Listening)
|
||||||
|
throw (new Exception("Socket Server is already listening. You cant change this property while listening."));
|
||||||
|
|
||||||
|
_keepAliveTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keep-alive interval in seconds
|
||||||
|
/// </summary>
|
||||||
|
public int KeepAliveInterval
|
||||||
|
{
|
||||||
|
get { return _keepAliveInterval; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Listening)
|
||||||
|
throw (new Exception("Socket Server is already listening. You cant change this property while listening."));
|
||||||
|
|
||||||
|
_keepAliveInterval = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int KeepAliveRetryCount
|
||||||
|
{
|
||||||
|
get { return _keepAliveRetryCount; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Listening)
|
||||||
|
throw (new Exception("Socket Server is already listening. You cant change this property while listening."));
|
||||||
|
|
||||||
|
_keepAliveRetryCount = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int ReceiveBufferSize
|
||||||
|
{
|
||||||
|
get { return _receiveBufferSize; }
|
||||||
|
set { _receiveBufferSize = value; }
|
||||||
|
}
|
||||||
|
public int ReceiveTimeout
|
||||||
|
{
|
||||||
|
get { return _receiveTimeout; }
|
||||||
|
set { _receiveTimeout = value; }
|
||||||
|
}
|
||||||
|
public int SendBufferSize
|
||||||
|
{
|
||||||
|
get { return _sendBufferSize; }
|
||||||
|
set { _sendBufferSize = value; }
|
||||||
|
}
|
||||||
|
public int SendTimeout
|
||||||
|
{
|
||||||
|
get { return _sendTimeout; }
|
||||||
|
set { _sendTimeout = value; }
|
||||||
|
}
|
||||||
|
public long BytesReceived
|
||||||
|
{
|
||||||
|
get { return _bytesReceived; }
|
||||||
|
internal set { _bytesReceived = value; }
|
||||||
|
}
|
||||||
|
public long BytesSent
|
||||||
|
{
|
||||||
|
get { return _bytesSent; }
|
||||||
|
internal set { _bytesSent = value; }
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Properties
|
||||||
|
private bool _isListening;
|
||||||
|
private int _port;
|
||||||
|
private bool _nodelay = true;
|
||||||
|
private bool _keepAlive = false;
|
||||||
|
private int _keepAliveTime = 900;
|
||||||
|
private int _keepAliveInterval = 300;
|
||||||
|
private int _keepAliveRetryCount = 5;
|
||||||
|
private int _receiveBufferSize = 8192;
|
||||||
|
private int _receiveTimeout = 0;
|
||||||
|
private int _sendBufferSize = 8192;
|
||||||
|
private int _sendTimeout = 0;
|
||||||
|
private long _bytesReceived = 0;
|
||||||
|
private long _bytesSent = 0;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Events
|
||||||
|
public event EventHandler<OnServerStartedEventArgs> OnStarted = delegate { };
|
||||||
|
public event EventHandler<OnServerStoppedEventArgs> OnStopped = delegate { };
|
||||||
|
public event EventHandler<OnServerErrorEventArgs> OnError = delegate { };
|
||||||
|
public event EventHandler<OnServerConnectionRequestEventArgs> OnConnectionRequest = delegate { };
|
||||||
|
public event EventHandler<OnServerConnectedEventArgs> OnConnected = delegate { };
|
||||||
|
public event EventHandler<OnServerDisconnectedEventArgs> OnDisconnected = delegate { };
|
||||||
|
public event EventHandler<OnServerDataReceivedEventArgs> OnDataReceived = delegate { };
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Invokers
|
||||||
|
internal void InvokeOnStarted(OnServerStartedEventArgs args) => this.OnStarted?.Invoke(this, args);
|
||||||
|
internal void InvokeOnStopped(OnServerStoppedEventArgs args) => this.OnStopped?.Invoke(this, args);
|
||||||
|
internal void InvokeOnError(OnServerErrorEventArgs args) => this.OnError?.Invoke(this, args);
|
||||||
|
internal void InvokeOnConnectionRequest(OnServerConnectionRequestEventArgs args) => this.OnConnectionRequest?.Invoke(this, args);
|
||||||
|
internal void InvokeOnConnected(OnServerConnectedEventArgs args) => this.OnConnected?.Invoke(this, args);
|
||||||
|
internal void InvokeOnDisconnected(OnServerDisconnectedEventArgs args) => this.OnDisconnected?.Invoke(this, args);
|
||||||
|
internal void InvokeOnDataReceived(OnServerDataReceivedEventArgs args) => this.OnDataReceived?.Invoke(this, args);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Readonly Properties
|
||||||
|
private TcpListener _listener;
|
||||||
|
private readonly SnowflakeGenerator _idGenerator;
|
||||||
|
private readonly ConcurrentDictionary<string, ConnectedClient> _clients;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Listener Task
|
||||||
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
|
private CancellationToken _cancellationToken;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
public TcpSharpSocketServer() : this(1024)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TcpSharpSocketServer(int port)
|
||||||
|
{
|
||||||
|
this.Port = port;
|
||||||
|
|
||||||
|
this._idGenerator = new SnowflakeGenerator();
|
||||||
|
this._clients = new ConcurrentDictionary<string, ConnectedClient>();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
public void StartListening()
|
||||||
|
{
|
||||||
|
this._clients.Clear();
|
||||||
|
|
||||||
|
this._cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
this._cancellationToken = this._cancellationTokenSource.Token;
|
||||||
|
Task.Factory.StartNew(ListeningTask, TaskCreationOptions.LongRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopListening()
|
||||||
|
{
|
||||||
|
// Disconnect All Clients
|
||||||
|
var connectionIds = _clients.Keys.ToList();
|
||||||
|
foreach (var connectionId in connectionIds)
|
||||||
|
{
|
||||||
|
Disconnect(connectionId, DisconnectReason.ServerStopped);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop Listener
|
||||||
|
this._listener.Stop();
|
||||||
|
this.Listening = false;
|
||||||
|
|
||||||
|
// Stop Thread
|
||||||
|
this._cancellationTokenSource.Cancel();
|
||||||
|
|
||||||
|
// Invoke OnStopped
|
||||||
|
InvokeOnStopped(new OnServerStoppedEventArgs
|
||||||
|
{
|
||||||
|
IsStopped = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectedClient GetClient(string connectionId)
|
||||||
|
{
|
||||||
|
// Check Point
|
||||||
|
if (!_clients.ContainsKey(connectionId)) return null;
|
||||||
|
|
||||||
|
// Return Client
|
||||||
|
return _clients[connectionId];
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendBytes(string connectionId, byte[] bytes)
|
||||||
|
{
|
||||||
|
// Get Client
|
||||||
|
var client = GetClient(connectionId);
|
||||||
|
if (client == null) return 0;
|
||||||
|
|
||||||
|
// Send Bytes
|
||||||
|
return client.SendBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> SendBytesAsync(string connectionId, byte[] bytes, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return SendBytes(connectionId, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendString(string connectionId, string data)
|
||||||
|
{
|
||||||
|
// Get Client
|
||||||
|
var client = GetClient(connectionId);
|
||||||
|
if (client == null) return 0;
|
||||||
|
|
||||||
|
// Send Bytes
|
||||||
|
return client.SendString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> SendStringAsync(string connectionId, string data, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return SendString(connectionId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendString(string connectionId, string data, Encoding encoding)
|
||||||
|
{
|
||||||
|
// Get Client
|
||||||
|
var client = GetClient(connectionId);
|
||||||
|
if (client == null) return 0;
|
||||||
|
|
||||||
|
// Send Bytes
|
||||||
|
return client.SendString(data, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> SendStringAsync(string connectionId, string data, Encoding encoding, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return SendString(connectionId, data, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendFile(string connectionId, string fileName)
|
||||||
|
{
|
||||||
|
// Get Client
|
||||||
|
var client = GetClient(connectionId);
|
||||||
|
if (client == null) return 0;
|
||||||
|
|
||||||
|
// Send Bytes
|
||||||
|
return client.SendFile(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> SendFileAsync(string connectionId, string fileName, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return SendFile(connectionId, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long SendFile(string connectionId, string fileName, byte[] preBuffer, byte[] postBuffer, TransmitFileOptions flags)
|
||||||
|
{
|
||||||
|
// Get Client
|
||||||
|
var client = GetClient(connectionId);
|
||||||
|
if (client == null) return 0;
|
||||||
|
|
||||||
|
// Send Bytes
|
||||||
|
return client.SendFile(fileName, preBuffer, postBuffer, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> SendFileAsync(string connectionId, string fileName, byte[] preBuffer, byte[] postBuffer, TransmitFileOptions flags, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return SendFile(connectionId, fileName, preBuffer, postBuffer, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect(string connectionId, DisconnectReason reason = DisconnectReason.None)
|
||||||
|
{
|
||||||
|
// Check Point
|
||||||
|
if (!_clients.ContainsKey(connectionId)) return;
|
||||||
|
|
||||||
|
// Get Client
|
||||||
|
var client = _clients[connectionId];
|
||||||
|
|
||||||
|
// Check Point
|
||||||
|
// if (!client.Connected) return;
|
||||||
|
|
||||||
|
// Stop Receiving
|
||||||
|
client.StopReceiving();
|
||||||
|
|
||||||
|
// Disconnect
|
||||||
|
this.Disconnect(client.Client);
|
||||||
|
|
||||||
|
// Remove From Clients
|
||||||
|
_clients.TryRemove(connectionId, out _);
|
||||||
|
|
||||||
|
// Invoke OnDisconnected
|
||||||
|
InvokeOnDisconnected(new OnServerDisconnectedEventArgs
|
||||||
|
{
|
||||||
|
ConnectionId = connectionId,
|
||||||
|
Reason = reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Methods
|
||||||
|
internal void AddReceivedBytes(long bytesCount)
|
||||||
|
{
|
||||||
|
Interlocked.Add(ref _bytesReceived, bytesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddSentBytes(long bytesCount)
|
||||||
|
{
|
||||||
|
Interlocked.Add(ref _bytesSent, bytesCount);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
private void Disconnect(TcpClient client)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.GetStream().Close();
|
||||||
|
client.Close();
|
||||||
|
client.Dispose();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ListeningTask()
|
||||||
|
{
|
||||||
|
this._listener = new TcpListener(IPAddress.Any, this.Port);
|
||||||
|
|
||||||
|
// NoDelay
|
||||||
|
this._listener.Server.NoDelay = this.NoDelay;
|
||||||
|
|
||||||
|
/* Keep Alive */
|
||||||
|
if (this.KeepAlive && this.KeepAliveInterval > 0)
|
||||||
|
{
|
||||||
|
#if NETCOREAPP3_0_OR_GREATER || NET5_0_OR_GREATER
|
||||||
|
_listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
|
||||||
|
_listener.Server.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, this.KeepAliveTime);
|
||||||
|
_listener.Server.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, this.KeepAliveInterval);
|
||||||
|
_listener.Server.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, this.KeepAliveRetryCount);
|
||||||
|
#elif NETFRAMEWORK
|
||||||
|
// Get the size of the uint to use to back the byte array
|
||||||
|
// int size = Marshal.SizeOf((uint)0);
|
||||||
|
int size = sizeof(uint);
|
||||||
|
|
||||||
|
// Create the byte array
|
||||||
|
byte[] keepAlive = new byte[size * 3];
|
||||||
|
|
||||||
|
// Pack the byte array:
|
||||||
|
// Turn keepalive on
|
||||||
|
Buffer.BlockCopy(BitConverter.GetBytes((uint)1), 0, keepAlive, 0, size);
|
||||||
|
|
||||||
|
// How long does it take to start the first probe (in milliseconds)
|
||||||
|
Buffer.BlockCopy(BitConverter.GetBytes((uint)(KeepAliveTime*1000)), 0, keepAlive, size, size);
|
||||||
|
|
||||||
|
// Detection time interval (in milliseconds)
|
||||||
|
Buffer.BlockCopy(BitConverter.GetBytes((uint)(KeepAliveInterval*1000)), 0, keepAlive, size * 2, size);
|
||||||
|
|
||||||
|
// Set the keep-alive settings on the underlying Socket
|
||||||
|
_listener.Server.IOControl(IOControlCode.KeepAliveValues, keepAlive, null);
|
||||||
|
#elif NETSTANDARD
|
||||||
|
// Set the keep-alive settings on the underlying Socket
|
||||||
|
_listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start
|
||||||
|
this._listener.Start();
|
||||||
|
|
||||||
|
// Listening
|
||||||
|
this.Listening = true;
|
||||||
|
|
||||||
|
// Invoke OnStarted Event
|
||||||
|
InvokeOnStarted(new OnServerStartedEventArgs
|
||||||
|
{
|
||||||
|
IsStarted = true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Loop for new connections
|
||||||
|
while (!this._cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Getting new connections
|
||||||
|
var tcpClient = this._listener.AcceptTcpClient();
|
||||||
|
var ipEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint;
|
||||||
|
var ipAddress = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address.ToString();
|
||||||
|
var port = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port;
|
||||||
|
var cr_args = new OnServerConnectionRequestEventArgs
|
||||||
|
{
|
||||||
|
IPEndPoint = ipEndPoint,
|
||||||
|
IPAddress = ipAddress,
|
||||||
|
Port = port,
|
||||||
|
Accept = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Decide Accept or Reject
|
||||||
|
// Invoke OnConnectionRequest Event
|
||||||
|
InvokeOnConnectionRequest(cr_args);
|
||||||
|
|
||||||
|
// Reject
|
||||||
|
if (!cr_args.Accept)
|
||||||
|
{
|
||||||
|
this.Disconnect(tcpClient);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept
|
||||||
|
tcpClient.NoDelay = this.NoDelay;
|
||||||
|
tcpClient.ReceiveBufferSize = this.ReceiveBufferSize;
|
||||||
|
tcpClient.ReceiveTimeout = this.ReceiveTimeout;
|
||||||
|
tcpClient.SendBufferSize = this.SendBufferSize;
|
||||||
|
tcpClient.SendTimeout = this.SendTimeout;
|
||||||
|
var nanoClient = new ConnectedClient(this, tcpClient, this._idGenerator.GenerateId().ToString());
|
||||||
|
this._clients[nanoClient.ConnectionId] = nanoClient;
|
||||||
|
|
||||||
|
// Start Receiving
|
||||||
|
nanoClient.StartReceiving();
|
||||||
|
|
||||||
|
// Invoke OnConnected Event
|
||||||
|
var c_args = new OnServerConnectedEventArgs
|
||||||
|
{
|
||||||
|
IPEndPoint = ipEndPoint,
|
||||||
|
IPAddress = ipAddress,
|
||||||
|
Port = port,
|
||||||
|
ConnectionId = nanoClient.ConnectionId
|
||||||
|
};
|
||||||
|
InvokeOnConnected(c_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listening
|
||||||
|
this.Listening = false;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
global using System;
|
||||||
|
global using System.Collections.Concurrent;
|
||||||
|
global using System.Collections.Generic;
|
||||||
|
global using System.IO;
|
||||||
|
global using System.Linq;
|
||||||
|
global using System.Net;
|
||||||
|
global using System.Net.Sockets;
|
||||||
|
global using System.Text;
|
||||||
|
global using System.Threading;
|
||||||
|
global using System.Threading.Tasks;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Acr.UserDialogs;
|
||||||
|
using qController.Communication;
|
||||||
|
|
||||||
|
namespace qController.Dialogs
|
||||||
|
{
|
||||||
|
public class AddInstancePrompt : PromptConfig
|
||||||
|
{
|
||||||
|
public AddInstancePrompt()
|
||||||
|
{
|
||||||
|
|
||||||
|
Title = "Enter IP of QLab Computer";
|
||||||
|
Message = "Enter an IP Address to save";
|
||||||
|
OkText = "Next";
|
||||||
|
OnTextChanged = args =>
|
||||||
|
{
|
||||||
|
//IPAddress ugh
|
||||||
|
args.IsValid = IPHelper.IsValidAddress(args.Value);
|
||||||
|
};
|
||||||
|
OnAction = (qAddress) =>
|
||||||
|
{
|
||||||
|
if (!qAddress.Ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UserDialogs.Instance.Prompt(new PromptConfig
|
||||||
|
{
|
||||||
|
Title = "Enter Name",
|
||||||
|
Message = "Enter a Name for " + qAddress.Text,
|
||||||
|
OkText = "Save",
|
||||||
|
OnAction = (qName) => {
|
||||||
|
Console.WriteLine("manual workspace added");
|
||||||
|
if (!qName.Ok)
|
||||||
|
return;
|
||||||
|
QStorage.AddInstance(qName.Text, qAddress.Text);
|
||||||
|
App.showToast("Manual Workspace Added!");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using Acr.UserDialogs;
|
||||||
|
using Microsoft.Maui.ApplicationModel;
|
||||||
|
|
||||||
|
namespace qController.Dialogs
|
||||||
|
{
|
||||||
|
public class DonatePrompt : ConfirmConfig
|
||||||
|
{
|
||||||
|
public DonatePrompt()
|
||||||
|
{
|
||||||
|
|
||||||
|
Title = "Support the Project";
|
||||||
|
Message = "This application will never be a paid app or contain any sort of ads, but if you choose to show your support you may do so by clicking the Donate button below. Thank you!";
|
||||||
|
OkText = "Donate";
|
||||||
|
OnAction = (response) =>
|
||||||
|
{
|
||||||
|
if (response)
|
||||||
|
Launcher.OpenAsync(new Uri("https://linktr.ee/JoelWetzell"));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using Acr.UserDialogs;
|
||||||
|
|
||||||
|
namespace qController.Dialogs
|
||||||
|
{
|
||||||
|
public class NoWorkspacesConfig : ConfirmConfig
|
||||||
|
{
|
||||||
|
public NoWorkspacesConfig(Action<bool> action)
|
||||||
|
{
|
||||||
|
Message = "QLab doesn't have any workspaces open?";
|
||||||
|
OkText = "Disconnect";
|
||||||
|
OnAction = action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Acr.UserDialogs;
|
||||||
|
using qController.QItems;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace qController.Dialogs
|
||||||
|
{
|
||||||
|
public class WorkspacePromptArgs : EventArgs
|
||||||
|
{
|
||||||
|
public QWorkspace SelectedWorkspace
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WorkspacePrompt
|
||||||
|
{
|
||||||
|
public delegate void WorkspaceSelectedHandler(object source, WorkspacePromptArgs args);
|
||||||
|
public event WorkspaceSelectedHandler WorkspaceSelected;
|
||||||
|
|
||||||
|
public WorkspacePrompt()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionSheetConfig getActionSheetConfigForWorkspaces(List<QWorkspaceInfo> workspaces)
|
||||||
|
{
|
||||||
|
ActionSheetConfig actionSheetConfig = new ActionSheetConfig();
|
||||||
|
actionSheetConfig.SetTitle("Select Workspace");
|
||||||
|
for (int i = 0; i < workspaces.Count; i++)
|
||||||
|
{
|
||||||
|
QWorkspaceInfo workspace = workspaces[i];
|
||||||
|
actionSheetConfig.Add(workspace.displayName, new Action(() => {
|
||||||
|
Log.Debug("CONTROLPAGE - Workspace Selected " + workspace.displayName);
|
||||||
|
|
||||||
|
|
||||||
|
if (!workspace.hasPasscode)
|
||||||
|
{
|
||||||
|
OnWorkspaceSelected(new QWorkspace(workspace.uniqueID));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
promptWorkspacePasscode(workspace.uniqueID);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return actionSheetConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void promptWorkspacePasscode(string workspace_id)
|
||||||
|
{
|
||||||
|
UserDialogs.Instance.Prompt(new PromptConfig
|
||||||
|
{
|
||||||
|
InputType = InputType.Number,
|
||||||
|
MaxLength = 4,
|
||||||
|
Title = "Enter Workspace Passcode",
|
||||||
|
OkText = "Connect",
|
||||||
|
IsCancellable = true,
|
||||||
|
OnAction = (resp) =>
|
||||||
|
{
|
||||||
|
if (resp.Ok)
|
||||||
|
{
|
||||||
|
OnWorkspaceSelected(new QWorkspace(workspace_id,resp.Value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnWorkspaceSelected(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnWorkspaceSelected(QWorkspace workspace)
|
||||||
|
{
|
||||||
|
if (WorkspaceSelected != null)
|
||||||
|
WorkspaceSelected(this, new WorkspacePromptArgs() { SelectedWorkspace = workspace });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace qController.QItems
|
||||||
|
{
|
||||||
|
public class QCue
|
||||||
|
{
|
||||||
|
//info loaded from "/cueLists"
|
||||||
|
public string number { get; set; }
|
||||||
|
public string uniqueID { get; set; }
|
||||||
|
public bool flagged { get; set; }
|
||||||
|
public string listName { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
public string colorName { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public bool armed { get; set; }
|
||||||
|
|
||||||
|
//info loaded from "/valuesForKeys
|
||||||
|
public decimal translationX { get; set; }
|
||||||
|
public bool isRunning { get; set; }
|
||||||
|
public decimal scaleX { get; set; }
|
||||||
|
public bool isPaused { get; set; }
|
||||||
|
public decimal translationY { get; set; }
|
||||||
|
public decimal preWait { get; set; }
|
||||||
|
public decimal opacity { get; set; }
|
||||||
|
public decimal scaleY { get; set; }
|
||||||
|
public decimal duration { get; set; }
|
||||||
|
public decimal postWait { get; set; }
|
||||||
|
public bool isBroken { get; set; }
|
||||||
|
public string displayName { get; set; }
|
||||||
|
public bool isLoaded { get; set; }
|
||||||
|
public string notes { get; set; }
|
||||||
|
public List<List<double>> levels { get; set; }
|
||||||
|
|
||||||
|
//info loaded from /children
|
||||||
|
public List<QCue> cues { get; set; }
|
||||||
|
|
||||||
|
//get string for icon font from cue type
|
||||||
|
public string getIconString()
|
||||||
|
{
|
||||||
|
return QIcon.GetIconFromType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string IconText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return QIcon.GetIconFromType(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace qController.QItems
|
||||||
|
{
|
||||||
|
public class QCueList
|
||||||
|
{
|
||||||
|
public string number { get; set; }
|
||||||
|
public string uniqueID { get; set; }
|
||||||
|
public List<QCue> cues { get; set; }
|
||||||
|
public bool flagged { get; set; }
|
||||||
|
public string listName { get; set; }
|
||||||
|
public string type { get; set; }
|
||||||
|
public string colorName { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public bool armed { get; set; }
|
||||||
|
|
||||||
|
public string IconText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return QIcon.GetIconFromType("Group");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
namespace qController.Classes.Cues
|
||||||
|
{
|
||||||
|
public class QDefinitions
|
||||||
|
{
|
||||||
|
public QDefinitions()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
namespace qController.QItems
|
||||||
|
{
|
||||||
|
public class QInstance
|
||||||
|
{
|
||||||
|
public string name { get; set; }
|
||||||
|
public string address { get; set; }
|
||||||
|
public QInstance()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public QInstance(string tName, string tAddress)
|
||||||
|
{
|
||||||
|
name = tName;
|
||||||
|
address = tAddress;
|
||||||
|
}
|
||||||
|
public bool IsReachable()
|
||||||
|
{
|
||||||
|
/*try
|
||||||
|
{
|
||||||
|
IPAddress instanceIP = IPAddress.Parse(address);
|
||||||
|
Ping p = new System.Net.NetworkInformation.Ping();
|
||||||
|
PingReply reply = p.Send(instanceIP);
|
||||||
|
return reply.Status == IPStatus.Success ? true : false;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}*/
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
//Class pertains to the local copy of a workspace, with methods for fetching/updating information inside
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace qController.QItems
|
||||||
|
{
|
||||||
|
public class QWorkspace
|
||||||
|
{
|
||||||
|
public string status { get; set; }
|
||||||
|
public List<QCueList> data { get; set; }
|
||||||
|
public string workspace_id { get; set; }
|
||||||
|
public string address { get; set; }
|
||||||
|
public bool IsPopulated { get; set; }
|
||||||
|
|
||||||
|
public string passcode { get; set; }
|
||||||
|
|
||||||
|
//ONLY FOR PASSING A WORKSPACE LOAD ERROR
|
||||||
|
public QWorkspace(string id)
|
||||||
|
{
|
||||||
|
workspace_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QWorkspace(string id, string passcode)
|
||||||
|
{
|
||||||
|
workspace_id = id;
|
||||||
|
this.passcode = passcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QWorkspace()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public QCue GetCue(string cue_id)
|
||||||
|
{
|
||||||
|
foreach (var cueList in data)
|
||||||
|
{
|
||||||
|
foreach (var cue in cueList.cues)
|
||||||
|
{
|
||||||
|
if(cue.uniqueID == cue_id)
|
||||||
|
{
|
||||||
|
return cue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QCueList GetCueList(string id)
|
||||||
|
{
|
||||||
|
foreach (var cueList in data)
|
||||||
|
{
|
||||||
|
if(cueList.uniqueID == id)
|
||||||
|
{
|
||||||
|
return cueList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateCue(QCue cue)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < data.Count; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < data[i].cues.Count; j++)
|
||||||
|
{
|
||||||
|
if(data[i].cues[j].uniqueID == cue.uniqueID)
|
||||||
|
{
|
||||||
|
//Log.Debug("Cue found and updated in workspace: " + cue.uniqueID);
|
||||||
|
data[i].cues[j] = cue;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateChildren(string cue_id, List<QCue> children)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < data.Count; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < data[i].cues.Count; j++)
|
||||||
|
{
|
||||||
|
if (data[i].cues[j].uniqueID == cue_id)
|
||||||
|
{
|
||||||
|
if(data[i].cues[j].type == "Group")
|
||||||
|
{
|
||||||
|
data[i].cues[j].cues = children;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CheckPopulated()
|
||||||
|
{
|
||||||
|
if(data == null)
|
||||||
|
{
|
||||||
|
IsPopulated = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < data.Count; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < data[i].cues.Count; j++)
|
||||||
|
{
|
||||||
|
if (data[i].cues[j].type == "Group" && data[i].cues[j].cues == null)
|
||||||
|
{
|
||||||
|
IsPopulated = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IsPopulated = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintStats()
|
||||||
|
{
|
||||||
|
foreach (var cueList in data)
|
||||||
|
{
|
||||||
|
Log.Debug(cueList.listName + "("+ cueList.cues.Count + " cues)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
//General information about a QLab Workspace
|
||||||
|
namespace qController.QItems
|
||||||
|
{
|
||||||
|
public class QWorkspaceInfo
|
||||||
|
{
|
||||||
|
public string version { get; set; }
|
||||||
|
public string displayName { get; set; }
|
||||||
|
public string uniqueID { get; set; }
|
||||||
|
public bool hasPasscode { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
namespace qController
|
||||||
|
{
|
||||||
|
public class MenuPageItem
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Icon { get; set; }
|
||||||
|
public string Command { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using qController.QItems;
|
||||||
|
|
||||||
|
namespace qController
|
||||||
|
{
|
||||||
|
public class NoQCueSelected : QCue
|
||||||
|
{
|
||||||
|
public NoQCueSelected()
|
||||||
|
{
|
||||||
|
listName = "No Cue Selected";
|
||||||
|
type = "";
|
||||||
|
notes = "Workspace has loaded but no cue is selected";
|
||||||
|
number = "!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
namespace qController
|
||||||
|
{
|
||||||
|
public class OSCListItem
|
||||||
|
{
|
||||||
|
public string Text { get; set; }
|
||||||
|
public string Icon { get; set; }
|
||||||
|
public string Command { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user