mirror of
https://github.com/jwetzell/qController.git
synced 2026-07-26 10:38:50 +00:00
initial working maui migration with android only
This commit is contained in:
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
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29424.173
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.12.35707.178
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qController.iOS", "iOS\qController.iOS.csproj", "{A30F10F2-4CBC-47E6-841D-8DCAD05BAA0D}"
|
||||
EndProject
|
||||
@@ -8,6 +8,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qController.Droid", "Droid\
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "qController", "qController.Standard\qController.csproj", "{86160AAC-2150-41B0-BB06-4442CA054A78}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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|iPhoneSimulator.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
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
|
||||
SolutionGuid = {3DBBFC12-E39D-4331-B179-FF5D0493949C}
|
||||
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,184 @@
|
||||
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);
|
||||
QCue cue = JsonConvert.DeserializeObject<QCue>(cueUpdate.ToString());
|
||||
OnCueInfoUpdated(cue);
|
||||
}
|
||||
|
||||
public void ParseSelectedCueInfo(OscMessage msg){
|
||||
JToken selectedCue = OSC2JSON(msg);
|
||||
QCue cue = JsonConvert.DeserializeObject<QCue>(selectedCue.ToString());
|
||||
OnSelectedCueUpdated(cue);
|
||||
}
|
||||
|
||||
public void ParseQInfo(OscMessage msg)
|
||||
{
|
||||
JToken qInfo = OSC2JSON(msg);
|
||||
List<QWorkspaceInfo> qWorkspaceInfo = JsonConvert.DeserializeObject<List<QWorkspaceInfo>>(qInfo.ToString());
|
||||
OnWorkspaceInfoReceived(qWorkspaceInfo);
|
||||
}
|
||||
|
||||
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);
|
||||
List<QCue> children = JsonConvert.DeserializeObject<List<QCue>>(children_obj.ToString());
|
||||
OnChildrenUpdated(cue_id, children);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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,160 @@
|
||||
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 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,494 @@
|
||||
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 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;
|
||||
|
||||
public static List<byte[]> Decode(byte[] data)
|
||||
{
|
||||
List<byte[]> messages = new List<byte[]>();
|
||||
|
||||
List<byte> buffer = 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,164 @@
|
||||
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;
|
||||
|
||||
private List<byte> frameStream = new List<byte>();
|
||||
|
||||
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)
|
||||
{
|
||||
frameStream.AddRange(e.Data);
|
||||
|
||||
int i = 0;
|
||||
int frameEnd = frameStream.FindIndex(1, frameByte => frameByte.Equals(SlipFrame.END));
|
||||
while (frameEnd > 0)
|
||||
{
|
||||
List<byte> frame = frameStream.GetRange(0, frameEnd + 1);
|
||||
frameStream.RemoveRange(0, frameEnd + 1);
|
||||
List<byte[]> messages = SlipFrame.Decode(frame.ToArray());
|
||||
foreach (var message in messages)
|
||||
{
|
||||
try
|
||||
{
|
||||
OscPacket packet = OscPacket.GetPacket(message);
|
||||
OscMessage responseMessage = (OscMessage)packet;
|
||||
if (packet == null)
|
||||
{
|
||||
_log.Error("packet is null");
|
||||
}
|
||||
|
||||
if (responseMessage == null)
|
||||
{
|
||||
_log.Error("responeMessage is null");
|
||||
}
|
||||
OnMessageReceived(responseMessage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error($"Exception parsing OSC message: {ex.ToString()}");
|
||||
}
|
||||
}
|
||||
frameEnd = frameStream.FindIndex(0, frameByte => frameByte.Equals(SlipFrame.END));
|
||||
}
|
||||
}
|
||||
|
||||
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