From e29267c5307ce70d5a108a785ebe3532d855fe2f Mon Sep 17 00:00:00 2001 From: y2keui Date: Tue, 14 Apr 2026 14:36:03 +0900 Subject: [PATCH] =?UTF-8?q?=ED=86=A0=EB=84=A4=EC=9D=B4=EB=8F=84=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20=EC=86=A1=EC=B6=9C=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tornado3_2026Election.slnx | 7 +- Tornado3_2026Election/Persistence/AppState.cs | 6 +- .../Services/KarismaTornado3Adapter.cs | 20 +- .../Services/TornadoManager.cs | 92 +++- .../Services/TornadoPathResolver.cs | 109 +++++ .../Tornado3_2026Election.csproj | 16 +- .../ViewModels/MainViewModel.cs | 38 +- .../ViewModels/SettingsViewModel.cs | 5 +- tools/KarismaTcpProbe/KarismaTcpProbe.csproj | 19 + tools/KarismaTcpProbe/Program.cs | 426 ++++++++++++++++++ 10 files changed, 703 insertions(+), 35 deletions(-) create mode 100644 Tornado3_2026Election/Services/TornadoPathResolver.cs create mode 100644 tools/KarismaTcpProbe/KarismaTcpProbe.csproj create mode 100644 tools/KarismaTcpProbe/Program.cs diff --git a/Tornado3_2026Election.slnx b/Tornado3_2026Election.slnx index 6f22afe..3c856ed 100644 --- a/Tornado3_2026Election.slnx +++ b/Tornado3_2026Election.slnx @@ -1,13 +1,12 @@ - - - - + + + diff --git a/Tornado3_2026Election/Persistence/AppState.cs b/Tornado3_2026Election/Persistence/AppState.cs index acd373b..01186c7 100644 --- a/Tornado3_2026Election/Persistence/AppState.cs +++ b/Tornado3_2026Election/Persistence/AppState.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using Tornado3_2026Election.Services; namespace Tornado3_2026Election.Persistence; @@ -10,7 +11,7 @@ public sealed class AppState public string SelectedStationId { get; set; } = "KNN"; - public string ImageRootPath { get; set; } = @"C:\Users\y2keu\Downloads\T3_Cut"; + public string ImageRootPath { get; set; } = TornadoPathResolver.GetDefaultT3CutPath(); public bool AutoRestoreSchedules { get; set; } = true; @@ -48,4 +49,3 @@ public sealed class AppState public Dictionary StationRegionFilters { get; set; } = []; } - diff --git a/Tornado3_2026Election/Services/KarismaTornado3Adapter.cs b/Tornado3_2026Election/Services/KarismaTornado3Adapter.cs index 69ccc7b..a3dd197 100644 --- a/Tornado3_2026Election/Services/KarismaTornado3Adapter.cs +++ b/Tornado3_2026Election/Services/KarismaTornado3Adapter.cs @@ -86,10 +86,20 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable logService.Info($"Karisma adapter using default port {DefaultKarismaPort}."); } - var t3CutPath = t3CutPathProvider(); + var configuredT3CutPath = t3CutPathProvider(); + var t3CutPath = string.IsNullOrWhiteSpace(configuredT3CutPath) + ? TornadoPathResolver.GetDefaultT3CutPath() + : TornadoPathResolver.NormalizeConfiguredPath(configuredT3CutPath); + + if (!string.Equals(configuredT3CutPath, t3CutPath, StringComparison.OrdinalIgnoreCase) && + !string.IsNullOrWhiteSpace(t3CutPath)) + { + logService.Info($"Karisma adapter normalized T3_Cut path to '{t3CutPath}'."); + } + if (string.IsNullOrWhiteSpace(t3CutPath) || !Directory.Exists(t3CutPath)) { - logService.Warning("Karisma adapter disabled: set a valid T3_Cut path in Settings."); + logService.Warning($"Karisma adapter disabled: set a valid T3_Cut path in Settings. current='{configuredT3CutPath}'"); adapter = new MockTornado3Adapter(logService); return false; } @@ -230,7 +240,11 @@ public sealed class KarismaTornado3Adapter : ITornado3Adapter, IDisposable private string ResolveT3CutPath() { - var path = _t3CutPathProvider(); + var configuredPath = _t3CutPathProvider(); + var path = string.IsNullOrWhiteSpace(configuredPath) + ? TornadoPathResolver.GetDefaultT3CutPath() + : TornadoPathResolver.NormalizeConfiguredPath(configuredPath); + if (string.IsNullOrWhiteSpace(path) || !Directory.Exists(path)) { throw new DirectoryNotFoundException("T3_Cut path is not configured or does not exist."); diff --git a/Tornado3_2026Election/Services/TornadoManager.cs b/Tornado3_2026Election/Services/TornadoManager.cs index c43dcae..b68a027 100644 --- a/Tornado3_2026Election/Services/TornadoManager.cs +++ b/Tornado3_2026Election/Services/TornadoManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using KAsyncEngineLib; @@ -372,9 +373,14 @@ public sealed class TornadoManager : IDisposable private sealed class StaDispatcher : IDisposable { - private readonly BlockingCollection _queue = new(); + private const uint WmApp = 0x8000; + private const uint WmDispatch = WmApp + 1; + + private readonly ConcurrentQueue _queue = new(); private readonly Thread _thread; + private readonly ManualResetEventSlim _threadReady = new(); private bool _disposed; + private uint _threadId; public StaDispatcher(string threadName) { @@ -385,6 +391,7 @@ public sealed class TornadoManager : IDisposable }; _thread.SetApartmentState(ApartmentState.STA); _thread.Start(); + _threadReady.Wait(); } public Task InvokeAsync(Action action, CancellationToken cancellationToken) @@ -402,18 +409,29 @@ public sealed class TornadoManager : IDisposable cancellationToken.ThrowIfCancellationRequested(); var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _queue.Add(() => + _queue.Enqueue(() => { + if (cancellationToken.IsCancellationRequested) + { + completion.TrySetCanceled(cancellationToken); + return; + } + try { - completion.SetResult(action()); + completion.TrySetResult(action()); } catch (Exception ex) { - completion.SetException(ex); + completion.TrySetException(ex); } }); + if (!PostThreadMessage(_threadId, WmDispatch, UIntPtr.Zero, IntPtr.Zero)) + { + throw new InvalidOperationException("Failed to signal Karisma STA dispatcher."); + } + return cancellationToken.CanBeCanceled ? completion.Task.WaitAsync(cancellationToken) : completion.Task; @@ -427,17 +445,77 @@ public sealed class TornadoManager : IDisposable } _disposed = true; - _queue.CompleteAdding(); + PostThreadMessage(_threadId, WmQuit, UIntPtr.Zero, IntPtr.Zero); _thread.Join(); - _queue.Dispose(); + _threadReady.Dispose(); } private void Run() { - foreach (var action in _queue.GetConsumingEnumerable()) + PeekMessage(out _, IntPtr.Zero, 0, 0, 0); + _threadId = GetCurrentThreadId(); + _threadReady.Set(); + + while (GetMessage(out var message, IntPtr.Zero, 0, 0) > 0) + { + if (message.message == WmDispatch) + { + DrainQueue(); + continue; + } + + TranslateMessage(ref message); + DispatchMessage(ref message); + } + + DrainQueue(); + } + + private void DrainQueue() + { + while (_queue.TryDequeue(out var action)) { action(); } } + + private const uint WmQuit = 0x0012; + + [DllImport("kernel32.dll")] + private static extern uint GetCurrentThreadId(); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool PostThreadMessage(uint idThread, uint msg, UIntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern int GetMessage(out NativeMessage lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); + + [DllImport("user32.dll")] + private static extern bool TranslateMessage([In] ref NativeMessage lpMsg); + + [DllImport("user32.dll")] + private static extern IntPtr DispatchMessage([In] ref NativeMessage lpMsg); + + [DllImport("user32.dll")] + private static extern bool PeekMessage(out NativeMessage lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); + + [StructLayout(LayoutKind.Sequential)] + private struct NativeMessage + { + public IntPtr hwnd; + public uint message; + public UIntPtr wParam; + public IntPtr lParam; + public uint time; + public NativePoint pt; + public uint lPrivate; + } + + [StructLayout(LayoutKind.Sequential)] + private struct NativePoint + { + public int x; + public int y; + } } } diff --git a/Tornado3_2026Election/Services/TornadoPathResolver.cs b/Tornado3_2026Election/Services/TornadoPathResolver.cs new file mode 100644 index 0000000..5544389 --- /dev/null +++ b/Tornado3_2026Election/Services/TornadoPathResolver.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Tornado3_2026Election.Services; + +public static class TornadoPathResolver +{ + public static string GetDefaultT3CutPath() + { + foreach (var candidate in GetCandidatePaths()) + { + var normalized = NormalizeConfiguredPath(candidate); + if (!string.IsNullOrWhiteSpace(normalized) && Directory.Exists(normalized)) + { + return normalized; + } + } + + return NormalizeConfiguredPath(Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + "Tornado3 Data", + "T3_Cut", + "T3_Cut")); + } + + public static string NormalizeConfiguredPath(string? configuredPath) + { + if (string.IsNullOrWhiteSpace(configuredPath)) + { + return string.Empty; + } + + var trimmedPath = configuredPath.Trim(); + if (!Directory.Exists(trimmedPath)) + { + return trimmedPath; + } + + var nestedDefault = Path.Combine(trimmedPath, "T3_Cut"); + if (ContainsSceneFiles(nestedDefault)) + { + return nestedDefault; + } + + if (ContainsSceneFiles(trimmedPath)) + { + return trimmedPath; + } + + var nestedDirectory = TryFindNestedSceneDirectory(trimmedPath); + return nestedDirectory ?? trimmedPath; + } + + private static IEnumerable GetCandidatePaths() + { + var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + var configured = Environment.GetEnvironmentVariable("TORNADO_T3CUT_PATH"); + + if (!string.IsNullOrWhiteSpace(configured)) + { + yield return configured; + } + + if (!string.IsNullOrWhiteSpace(documents)) + { + yield return Path.Combine(documents, "Tornado3 Data", "T3_Cut", "T3_Cut"); + yield return Path.Combine(documents, "Tornado3 Data", "T3_Cut"); + } + + if (!string.IsNullOrWhiteSpace(userProfile)) + { + yield return Path.Combine(userProfile, "Downloads", "T3_Cut"); + } + } + + private static string? TryFindNestedSceneDirectory(string rootPath) + { + try + { + return Directory.EnumerateDirectories(rootPath, "*", SearchOption.TopDirectoryOnly) + .Select(NormalizeConfiguredPath) + .FirstOrDefault(path => !string.IsNullOrWhiteSpace(path) && ContainsSceneFiles(path)); + } + catch + { + return null; + } + } + + private static bool ContainsSceneFiles(string? path) + { + if (string.IsNullOrWhiteSpace(path) || !Directory.Exists(path)) + { + return false; + } + + try + { + return Directory.EnumerateFiles(path, "*.tscn", SearchOption.AllDirectories).Any(); + } + catch + { + return false; + } + } +} diff --git a/Tornado3_2026Election/Tornado3_2026Election.csproj b/Tornado3_2026Election/Tornado3_2026Election.csproj index 193f0aa..f488be2 100644 --- a/Tornado3_2026Election/Tornado3_2026Election.csproj +++ b/Tornado3_2026Election/Tornado3_2026Election.csproj @@ -6,24 +6,20 @@ Tornado3_2026Election app.manifest Assets\AppIcon.ico - x86;x64;ARM64 - win-x86;win-x64;win-arm64 + x64 + win-x64 true false true enable - + win-x64 - - win-x86 - - - - win-arm64 + + C:\Karisma SDK @@ -89,7 +85,7 @@ - ..\..\..\..\..\..\Karisma SDK\Bin\C#\Interop.KAsyncEngineLib.dll + $(KarismaSdkDir)\Bin\C#\Interop.KAsyncEngineLib.dll diff --git a/Tornado3_2026Election/ViewModels/MainViewModel.cs b/Tornado3_2026Election/ViewModels/MainViewModel.cs index 7ca616f..91d29c6 100644 --- a/Tornado3_2026Election/ViewModels/MainViewModel.cs +++ b/Tornado3_2026Election/ViewModels/MainViewModel.cs @@ -24,6 +24,7 @@ public sealed class MainViewModel : ObservableObject private readonly FormatCatalogService _formatCatalogService; private readonly AppStateStore _stateStore; private readonly LogService _logService; + private readonly ITornado3Adapter _sharedTornadoAdapter; private readonly SemaphoreSlim _stateSaveLock = new(1, 1); private AppPage _currentPage = AppPage.IntegratedSchedule; private ChannelOperationMode _operationMode = ChannelOperationMode.General; @@ -56,11 +57,12 @@ public sealed class MainViewModel : ObservableObject Settings.PropertyChanged += Settings_PropertyChanged; Data.PropertyChanged += Data_PropertyChanged; + _sharedTornadoAdapter = KarismaTornado3Adapter.CreateOrFallback(_logService, () => Settings.ImageRootPath); - NormalChannel = CreateChannelViewModel(BroadcastChannel.Normal, "노멀"); - TopLeftChannel = CreateChannelViewModel(BroadcastChannel.TopLeft, "좌상단"); - BottomChannel = CreateChannelViewModel(BroadcastChannel.Bottom, "하단"); - VideoWallChannel = CreateChannelViewModel(BroadcastChannel.VideoWall, "비디오월"); + NormalChannel = CreateChannelViewModel(BroadcastChannel.Normal, "노멀", _sharedTornadoAdapter); + TopLeftChannel = CreateChannelViewModel(BroadcastChannel.TopLeft, "좌상단", _sharedTornadoAdapter); + BottomChannel = CreateChannelViewModel(BroadcastChannel.Bottom, "하단", _sharedTornadoAdapter); + VideoWallChannel = CreateChannelViewModel(BroadcastChannel.VideoWall, "비디오월", _sharedTornadoAdapter); Channels = [NormalChannel, TopLeftChannel, BottomChannel, VideoWallChannel]; foreach (var channel in Channels) @@ -82,6 +84,7 @@ public sealed class MainViewModel : ObservableObject _logService.Entries.CollectionChanged += (_, _) => RebuildFilteredLogs(); SelectedLogFilterOption = LogFilterOptions[0]; _logService.Info("SYSTEM_SPEC 기반 MVVM 구조를 초기화했습니다."); + _ = WarmupSharedCgConnectionAsync(); } public DataViewModel Data { get; } @@ -439,6 +442,7 @@ public sealed class MainViewModel : ObservableObject } _logService.Info("저장한 시작 옵션에 따라 상태를 자동 복원했습니다."); + _ = WarmupSharedCgConnectionAsync(); } public async Task RestoreAsync() @@ -471,6 +475,11 @@ public sealed class MainViewModel : ObservableObject public async Task ShutdownAsync() { await SaveStateCoreAsync(writeLog: false); + if (_sharedTornadoAdapter is IDisposable disposableAdapter) + { + disposableAdapter.Dispose(); + } + Data.Dispose(); } @@ -520,6 +529,7 @@ public sealed class MainViewModel : ObservableObject if (args.PropertyName is nameof(SettingsViewModel.SelectedStationId) or nameof(SettingsViewModel.ImageRootPath)) { QueueAutomaticSave(); + _ = WarmupSharedCgConnectionAsync(); } } @@ -694,9 +704,25 @@ public sealed class MainViewModel : ObservableObject } } - private ChannelScheduleViewModel CreateChannelViewModel(BroadcastChannel channel, string title) + private async Task WarmupSharedCgConnectionAsync() + { + if (!_sharedTornadoAdapter.IsLiveCg) + { + return; + } + + try + { + await _sharedTornadoAdapter.EnsureConnectedAsync(CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logService.Warning($"CG startup connection attempt failed: {ex.Message}"); + } + } + + private ChannelScheduleViewModel CreateChannelViewModel(BroadcastChannel channel, string title, ITornado3Adapter adapter) { - var adapter = KarismaTornado3Adapter.CreateOrFallback(_logService, () => Settings.ImageRootPath); var queue = new ObservableCollection(); var engine = new ChannelScheduleEngine( channel, diff --git a/Tornado3_2026Election/ViewModels/SettingsViewModel.cs b/Tornado3_2026Election/ViewModels/SettingsViewModel.cs index 33e0562..459caf4 100644 --- a/Tornado3_2026Election/ViewModels/SettingsViewModel.cs +++ b/Tornado3_2026Election/ViewModels/SettingsViewModel.cs @@ -3,13 +3,14 @@ using System.Collections.ObjectModel; using System.Linq; using Tornado3_2026Election.Common; using Tornado3_2026Election.Domain; +using Tornado3_2026Election.Services; namespace Tornado3_2026Election.ViewModels; public sealed class SettingsViewModel : ObservableObject { private string _selectedStationId = "KNN"; - private string _imageRootPath = @"C:\Users\y2keu\Downloads\T3_Cut"; + private string _imageRootPath = TornadoPathResolver.GetDefaultT3CutPath(); public SettingsViewModel(IEnumerable stations) { @@ -50,7 +51,7 @@ public sealed class SettingsViewModel : ObservableObject public string ImageRootPath { get => _imageRootPath; - set => SetProperty(ref _imageRootPath, value); + set => SetProperty(ref _imageRootPath, TornadoPathResolver.NormalizeConfiguredPath(value)); } public StationFilterItemViewModel SelectedStation diff --git a/tools/KarismaTcpProbe/KarismaTcpProbe.csproj b/tools/KarismaTcpProbe/KarismaTcpProbe.csproj new file mode 100644 index 0000000..dd01204 --- /dev/null +++ b/tools/KarismaTcpProbe/KarismaTcpProbe.csproj @@ -0,0 +1,19 @@ + + + Exe + net8.0-windows + x64 + x64 + win-x64 + enable + enable + C:\Karisma SDK + + + + + $(KarismaSdkDir)\Bin\C#\Interop.KAsyncEngineLib.dll + true + + + diff --git a/tools/KarismaTcpProbe/Program.cs b/tools/KarismaTcpProbe/Program.cs new file mode 100644 index 0000000..79b5cd4 --- /dev/null +++ b/tools/KarismaTcpProbe/Program.cs @@ -0,0 +1,426 @@ +using System; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using KAsyncEngineLib; + +var options = ProbeOptions.Parse(args); + +Console.WriteLine($"Karisma TCP probe starting. target={options.Host}:{options.Port} timeout={options.Timeout.TotalSeconds:0}s"); + +var rawTcpOk = await ProbeRawTcpAsync(options).ConfigureAwait(false); +var sdkResult = await ProbeSdkAsync(options).ConfigureAwait(false); + +Console.WriteLine(); +Console.WriteLine("Summary"); +Console.WriteLine($"- Raw TCP: {(rawTcpOk ? "OK" : "FAILED")}"); +Console.WriteLine($"- SDK Connect(): {(sdkResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}"); +Console.WriteLine($"- SDK OnConnect: {sdkResult.ConnectOutcome}"); +Console.WriteLine($"- SDK Detail: {sdkResult.Detail}"); + +Environment.ExitCode = rawTcpOk && sdkResult.ConnectRequestAccepted && sdkResult.ConnectOutcome == "SUCCESS" + ? 0 + : 1; + +static async Task ProbeRawTcpAsync(ProbeOptions options) +{ + try + { + using var client = new TcpClient(); + using var cts = new CancellationTokenSource(options.Timeout); + await client.ConnectAsync(options.Host, options.Port, cts.Token).ConfigureAwait(false); + Console.WriteLine($"[RAW] Connected local={client.Client.LocalEndPoint} remote={client.Client.RemoteEndPoint}"); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"[RAW] Failed: {ex.Message}"); + return false; + } +} + +static Task ProbeSdkAsync(ProbeOptions options) +{ + var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var thread = new Thread(() => + { + try + { + var handler = new ProbeEventHandler(); + var engine = (IKAEngine)new KAEngineClass(); + + Console.WriteLine("[SDK] Calling Connect()..."); + var connectRequested = engine.Connect(options.Host, options.Port, handler); + Console.WriteLine($"[SDK] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}"); + + if (connectRequested == 0) + { + completion.TrySetResult(new SdkProbeResult(false, "FAILED", "Connect() returned 0.")); + return; + } + + if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Timeout)) + { + completion.TrySetResult(new SdkProbeResult(true, "TIMEOUT", $"OnConnect was not received within {options.Timeout.TotalSeconds:0} seconds.")); + return; + } + + var errorCode = handler.ConnectTask.Result; + var outcome = errorCode == 0 ? "SUCCESS" : "FAILED"; + completion.TrySetResult(new SdkProbeResult(true, outcome, $"OnConnect errorCode={errorCode}")); + + try + { + engine.Disconnect(); + handler.CloseTask.Wait(TimeSpan.FromSeconds(2)); + } + catch + { + } + } + catch (Exception ex) + { + completion.TrySetResult(new SdkProbeResult(false, "EXCEPTION", ex.ToString())); + } + }) + { + IsBackground = true, + Name = "KarismaTcpProbe" + }; + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + return completion.Task; +} + +static bool WaitForTaskWithMessagePump(Task task, TimeSpan timeout) +{ + User32.PeekMessage(out _, IntPtr.Zero, 0, 0, 0); + + var deadline = DateTime.UtcNow + timeout; + while (!task.IsCompleted) + { + while (User32.PeekMessage(out var message, IntPtr.Zero, 0, 0, 1)) + { + User32.TranslateMessage(ref message); + User32.DispatchMessage(ref message); + } + + if (DateTime.UtcNow >= deadline) + { + return false; + } + + Thread.Sleep(10); + } + + return true; +} + +internal sealed record ProbeOptions(string Host, int Port, TimeSpan Timeout) +{ + public static ProbeOptions Parse(string[] args) + { + var host = "127.0.0.1"; + var port = 30001; + var timeout = TimeSpan.FromSeconds(5); + + for (var index = 0; index < args.Length; index++) + { + switch (args[index]) + { + case "--host" when index + 1 < args.Length: + host = args[++index]; + break; + case "--port" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedPort): + port = parsedPort; + index++; + break; + case "--timeout" when index + 1 < args.Length && double.TryParse(args[index + 1], out var parsedTimeoutSeconds): + timeout = TimeSpan.FromSeconds(parsedTimeoutSeconds); + index++; + break; + } + } + + return new ProbeOptions(host, port, timeout); + } +} + +internal sealed record SdkProbeResult(bool ConnectRequestAccepted, string ConnectOutcome, string Detail); + +internal sealed class ProbeEventHandler : KAEventHandler +{ + private readonly TaskCompletionSource _connectTask = new(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly TaskCompletionSource _closeTask = new(TaskCreationOptions.RunContinuationsAsynchronously); + + public Task ConnectTask => _connectTask.Task; + + public Task CloseTask => _closeTask.Task; + + public void OnConnect(int ErrorCode) + { + Console.WriteLine($"[SDK] OnConnect errorCode={ErrorCode}"); + _connectTask.TrySetResult(ErrorCode); + } + + public void OnClose(int ErrorCode) + { + Console.WriteLine($"[SDK] OnClose errorCode={ErrorCode}"); + _closeTask.TrySetResult(ErrorCode); + } + + public void OnLoadScene(eKResult Result, string SceneName) { } + public void OnLoadSceneForce(eKResult Result, string SceneName) { } + public void OnLogMessage(string LogMessage) { } + public void OnMessageNo(uint MessageNo) { } + public void OnBeginTransaction(eKResult Result) { } + public void OnEndTransaction(eKResult Result) { } + public void OnHeartBeat(eKResult Result) { } + public void OnUnloadAll(eKResult Result) { } + public void OnSetTrialPlayoutMode(eKResult Result) { } + public void OnCheckVersion(eKResult Result, string ServerVersion, string SDKVersion) { } + public void OnSetAudioOutput(eKResult Result) { } + public void OnScenePrepare(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnScenePrepareEx(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnPlay(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnPlayOut(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnStop(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnStopAll(eKResult Result) { } + public void OnPause(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnResume(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnQueryIsOnAir(eKResult Result, int OutputChannelIndex, int LayerNo, int bOnAir) { } + public void OnTrigger(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnScenePlayingStarted(string SceneName, int OutputChannelIndex, int LayerNo) { } + public void OnScenePlayed(string SceneName, int OutputChannelIndex, int LayerNo) { } + public void OnSceneAnimationPlayed(string SceneName, int OutputChannelIndex, int LayerNo, string AnimationName) { } + public void OnScenePaused(string SceneName, int OutputChannelIndex, int LayerNo, int bLastPause) { } + public void OnSceneSaved(eKResult Result, string FileName) { } + public void OnTriggerObject(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnResumeBackground(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnSaveMixedPreviewImage(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnPlayDirect(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnCutIn(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnCutOut(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnClearNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnPlayRange(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnQueryPlaybackRangeCount(eKResult Result, string SceneName, int PlaybackRangeCount) { } + public void OnQueryPlaybackRange(eKResult Result, string SceneName, int PlaybackRangeNo, int Start, int End) { } + public void OnQueryOutputChannelIndex(eKResult Result, string SceneName, int OutputChannelIndex) { } + public void OnPlayInNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnPlayOutNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnSetBackgroundFill(eKResult Result, string SceneName) { } + public void OnSetBackgroundTexture(eKResult Result, string SceneName) { } + public void OnSetBackgroundVideo(eKResult Result, string SceneName) { } + public void OnSetBackgroundLiveIn(eKResult Result, string SceneName) { } + public void OnUseBackground(eKResult Result, string SceneName) { } + public void OnSetBackgroundVideoPlayInfo(eKResult Result, string SceneName) { } + public void OnQueryBackgroundVideoPlayInfo(eKResult Result, string SceneName, ref sKVideoPlayInfo pVideoPlayInfo) { } + public void OnSetSceneEffectType(eKResult Result, string SceneName) { } + public void OnSaveSceneImage(eKResult Result, string SceneName) { } + public void OnSaveScene(eKResult Result, string SceneName) { } + public void OnUnloadScene(eKResult Result, string SceneName) { } + public void OnReloadScene(eKResult Result, string SceneName) { } + public void OnUpdateTextures(eKResult Result, string SceneName) { } + public void OnSetSceneAudioFile(eKResult Result, string SceneName) { } + public void OnEnableSceneAudio(eKResult Result, string SceneName) { } + public void OnSetSceneDuration(eKResult Result, string SceneName) { } + public void OnSetBackgroundPauseType(eKResult Result, string SceneName) { } + public void OnSetBackgroundChangeType(eKResult Result, string SceneName) { } + public void OnSetBackgroundPauseAtZeroFrameAsStandBy(eKResult Result, string SceneName) { } + public void OnResetDuration(eKResult Result, string SceneName) { } + public void OnSetDuration(eKResult Result, string SceneName) { } + public void OnAddObject(eKResult Result, string SceneName) { } + public void OnAddCloneObject(eKResult Result, string SceneName) { } + public void OnUpdateThumbnail(eKResult Result, string SceneName) { } + public void OnExportVideo(eKResult Result, string SceneName) { } + public void OnStopVideoExporting(eKResult Result) { } + public void OnQueryVideoExportingProgress(eKResult Result, string TargetName, int CurrentFrame, int TotalFrame) { } + public void OnFinishedVideoExporting(eKResult Result, string FileName) { } + public void OnAddPause(eKResult Result, string SceneName) { } + public void OnDeletePause(eKResult Result, string SceneName) { } + public void OnSetPause(eKResult Result, string SceneName) { } + public void OnSetPauseWithIndex(eKResult Result, string SceneName) { } + public void OnDeletePauseWithIndex(eKResult Result, string SceneName) { } + public void OnQueryPauseCount(eKResult Result, string SceneName, int PauseCount) { } + public void OnQueryObjectInfos(eKResult Result, string SceneName, KAObjectInfos pObjectInfos) { } + public void OnQueryAnimationNames(eKResult Result, string SceneName, KAStrings pAnimationNames) { } + public void OnQueryAnimationCount(eKResult Result, string SceneName, int AnimationCount) { } + public void OnQueryObjectInfosByScreenPoint(eKResult Result, KAObjectInfos pObjectInfos) { } + public void OnQuerySceneEffectType(eKResult Result, string SceneName, int bInEffect, eKEffectType EffectType, int Duration) { } + public void OnQueryDuration(eKResult Result, string SceneName, string AnimationName, int Duration) { } + public void OnQueryContentsOfTextObjects(eKResult Result, string SceneName, KAStrings pTexts) { } + public void OnSetStyleColor(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetStyleTexture(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetFaceTextColor(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetEdgeTextColor(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetShadowTextColor(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetVisible(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetValue(eKResult Result, string SceneName, string ObjectName) { } + public void OnAddText(eKResult Result, string SceneName, string ObjectName) { } + public void OnStoreTextStyle(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetTextStyle(eKResult Result, string SceneName, string ObjectName) { } + public void OnEditText(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetFont(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetTextRange(eKResult Result, string SceneName, string ObjectName) { } + public void OnResetTextRange(eKResult Result, string SceneName, string ObjectName) { } + public void OnQueryObjectType(eKResult Result, string SceneName, string ObjectName, eKObjectType ObjectType) { } + public void OnSetChartCSVFile(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetChartCellData(eKResult Result, string SceneName, string ObjectName) { } + public void OnQueryChartDataTable(eKResult Result, string SceneName, string ObjectName, KAChartDataTable Table) { } + public void OnQuerySize(eKResult Result, string SceneName, string ObjectName, float Width, float Height) { } + public void OnSetSize(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetCounterNumberKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetPositionKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetRotationKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetScaleKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetCylinderAngleKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetSphereAngleKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetCircleAngleKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetCropKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetCountDown(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetPosition(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetRotation(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetScale(eKResult Result, string SceneName, string ObjectName) { } + public void OnAddPathPoint(eKResult Result, string SceneName, string ObjectName) { } + public void OnClearPathPoints(eKResult Result, string SceneName, string ObjectName) { } + public void OnAddPathShapePoint(eKResult Result, string SceneName, string ObjectName) { } + public void OnClearPathShapePoints(eKResult Result, string SceneName, string ObjectName) { } + public void OnQueryScrollRemainingDistance(eKResult Result, string SceneName, string ObjectName, int ScrollRemainingDistance) { } + public void OnQueryScrollChildRemainingDistance(eKResult Result, string SceneName, string ObjectName, string ChildName, int ScrollRemainingDistance) { } + public void OnAddScrollObject(eKResult Result, string SceneName, string ObjectName) { } + public void OnAdjustScrollSpeed(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetScrollSpeed(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetVariableName(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetLoftPositionKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetChangeOut(eKResult Result, string SceneName) { } + public void OnModifyPathPoint(eKResult Result, string SceneName, string ObjectName) { } + public void OnInitScrollObject(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetCounterInfo(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetCounterNumber(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetCounterRange(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetCounterRemainingTime(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetCounterElapsedTime(eKResult Result, string SceneName, string ObjectName) { } + public void OnSaveObjectImage(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetPositionOfPathAnimation(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetPositionKeyOfPathAnimation(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetStartFrame(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetObjectEffectType(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetObjectOutEffectDelay(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetColor(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetColorKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetEmissiveColor(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetEmissiveColorKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetTransparencyOpacity(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetTransparencyOpacityKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetExposure(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetExposureKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureType(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureFile(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureOffset(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureOffsetKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureTiling(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureTilingKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureRotation(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureRotationKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureOpacity(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureOpacityKey(eKResult Result, string SceneName, string ObjectName) { } + public void OnQueryGroupType(eKResult Result, string SceneName, string ObjectName, eKGroupType GroupType) { } + public void OnQueryImageType(eKResult Result, string SceneName, string ObjectName, eKImageType ImageType) { } + public void OnSetVideoPlayInfo(eKResult Result, string SceneName, string ObjectName) { } + public void OnQueryVideoPlayInfo(eKResult Result, string SceneName, string ObjectName, ref sKVideoPlayInfo pVideoPlayInfo) { } + public void OnQueryIs3D(eKResult Result, string SceneName, string ObjectName, int b3D) { } + public void OnQueryPosition(eKResult Result, string SceneName, string ObjectName, float X, float Y, float Z) { } + public void OnSetImageType(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMemo(eKResult Result, string SceneName, string ObjectName) { } + public void OnQueryMemo(eKResult Result, string SceneName, string ObjectName, string Memo) { } + public void OnQueryFont(eKResult Result, string SceneName, string ObjectName, ref sKFont Param) { } + public void OnSetImageOriginalSize(eKResult Result, string SceneName, string ObjectName) { } + public void OnApplyChangeEffectLibrary(eKResult Result, string SceneName) { } + public void OnApplyObjectLibrary(eKResult Result, string SceneName, string ObjectName) { } + public void OnApplyTextureEffectLibrary(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetTableValue(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetTableColor(eKResult Result, string SceneName, string ObjectName) { } + public void OnQueryTableValue(eKResult Result, string SceneName, string ObjectName, int Row, int Column, string Value) { } + public void OnQueryTableValues(eKResult Result, string SceneName, string ObjectName, KATableValues pValues) { } + public void OnSetPathShapeOutlineThickness(eKResult Result, string SceneName, string ObjectName) { } + public void OnEnablePathShapeOutline(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetPlaybackCamera(eKResult Result, string SceneName) { } + public void OnSetMaterialTextureVideoPlayInfo(eKResult Result, string SceneName, string ObjectName) { } + public void OnQueryMaterialTextureVideoPlayInfo(eKResult Result, string SceneName, string ObjectName, ref sKVideoPlayInfo VideoPlayInfo) { } + public void OnQueryVideoFormat(eKResult Result, ref sKVideoFormat VideoFormat) { } + public void OnQueryLiveStreamingStatus(eKResult Result, string StreamingURI, eKLiveStreamingStatus Status) { } + public void OnPreloadLiveStreaming(eKResult Result, string StreamingURI) { } + public void OnReleaseLiveStreaming(eKResult Result, string StreamingURI) { } + public void OnUpdateImageResource(eKResult Result) { } + public void OnQueryLayerCount(eKResult Result, int LayerCount) { } + public void OnSetLayerViewportRate(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnSetLayerViewportRateEx(eKResult Result, int OutputChannelIndex, int LayerNo) { } + public void OnSetFitting(eKResult Result, string SceneName) { } + public void OnSetFittingOffset(eKResult Result, string SceneName) { } + public void OnSetFittingScale(eKResult Result, string SceneName) { } + public void OnSetLightColor(eKResult Result, string SceneName, string ObjectName) { } + public void OnEnableLight(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetDirectionalLight(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetPointLight(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetSpotLight(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetInfinitePointLight(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetMaterialTextureLiveStreamingURI(eKResult Result, string SceneName, string ObjectName) { } + public void OnSetBackgroundLiveStreamingURI(eKResult Result, string SceneName) { } + public void OnLoadProject(eKResult Result, string FilePath, string AliasName) { } + public void OnNewProject(eKResult Result, string AliasName) { } + public void OnUnloadAllProject(eKResult Result) { } + public void OnSaveProject(eKResult Result, string AliasName) { } + public void OnQuerySceneItemCount(eKResult Result, string AliasName, int SceneItemCount) { } + public void OnQuerySceneItemInfos(eKResult Result, string AliasName, KASceneItemInfos SceneItemInfos) { } + public void OnAddSceneItem(eKResult Result, string AliasName, int Index) { } + public void OnInsertSceneItem(eKResult Result, string AliasName) { } + public void OnDeleteSceneItem(eKResult Result, string AliasNAme) { } + public void OnQueryProjectFormat(eKResult Result, ref sKVideoFormat ProjectFormat) { } + public void OnSetTimecode(eKResult Result, string AliasName) { } + public void OnSetTimecodeInOut(eKResult Result, string AliasName) { } + public void OnSetTimecodeTrack(eKResult Result, string AliasName) { } + public void OnSetTimecodeInOutType(eKResult Result, string AliasName) { } + public void OnDeleteTimecode(eKResult Result, string AliasName) { } + public void OnQueryTimecode(eKResult Result, int TrackNo, int In, int Out, int bOnTrack) { } + public void OnUnloadProject(eKResult Result, string AliasName) { } + public void OnEnableSyncWithSceneEffect(eKResult Result, string AliasName) { } + public void OnExportProjectVideo(eKResult Result, string AliasName) { } + public void OnExportSceneImage(eKResult Result, string SceneName) { } + public void OnStartVideoCapture(eKResult Result) { } + public void OnStopVideoCapture(eKResult Result) { } + public void OnCaptureImage(eKResult Result) { } +} + +[StructLayout(LayoutKind.Sequential)] +internal struct NativeMessage +{ + public IntPtr hwnd; + public uint message; + public UIntPtr wParam; + public IntPtr lParam; + public uint time; + public NativePoint pt; + public uint lPrivate; +} + +[StructLayout(LayoutKind.Sequential)] +internal struct NativePoint +{ + public int x; + public int y; +} + +internal static class User32 +{ + [DllImport("user32.dll")] + internal static extern bool PeekMessage(out NativeMessage lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); + + [DllImport("user32.dll")] + internal static extern bool TranslateMessage([In] ref NativeMessage lpMsg); + + [DllImport("user32.dll")] + internal static extern IntPtr DispatchMessage([In] ref NativeMessage lpMsg); +}