From fa647e5fec9cd11c14ff2c0d7156306c2db59606 Mon Sep 17 00:00:00 2001 From: Luca Date: Fri, 10 May 2024 21:04:55 +0200 Subject: [PATCH] Show window on single instance kill --- ShockOsc/Cli/Uri/UriParameterType.cs | 1 + ShockOsc/HeadlessProgram.cs | 3 ++ ShockOsc/MauiProgram.cs | 41 +++++++++++++------ ShockOsc/Platforms/Windows/PipeHelper.cs | 9 ++-- ShockOsc/Platforms/Windows/WindowUtils.cs | 26 ++++++++++++ .../Platforms/Windows/WindowsEntryPoint.cs | 24 ++++++----- ShockOsc/Platforms/Windows/WindowsServices.cs | 3 +- .../Platforms/Windows/WindowsTrayService.cs | 26 +++++------- ShockOsc/Services/Pipes/PipeMessageType.cs | 1 + ShockOsc/Services/Pipes/PipeServerService.cs | 8 ++-- ShockOsc/Services/ShockOsc.cs | 19 +++++---- .../Ui/Pages/Authentication/LoginPart.razor | 5 ++- ShockOsc/Ui/Utils/DebouncedSlider.razor.cs | 2 + 13 files changed, 110 insertions(+), 58 deletions(-) create mode 100644 ShockOsc/Platforms/Windows/WindowUtils.cs diff --git a/ShockOsc/Cli/Uri/UriParameterType.cs b/ShockOsc/Cli/Uri/UriParameterType.cs index d0c4d8c..985f184 100644 --- a/ShockOsc/Cli/Uri/UriParameterType.cs +++ b/ShockOsc/Cli/Uri/UriParameterType.cs @@ -2,5 +2,6 @@ public enum UriParameterType { + Show, Token } \ No newline at end of file diff --git a/ShockOsc/HeadlessProgram.cs b/ShockOsc/HeadlessProgram.cs index be5f6e5..f9acb94 100644 --- a/ShockOsc/HeadlessProgram.cs +++ b/ShockOsc/HeadlessProgram.cs @@ -1,4 +1,7 @@ using Microsoft.Extensions.Hosting; +#if WINDOWS +using OpenShock.ShockOsc.Platforms.Windows; +#endif namespace OpenShock.ShockOsc; diff --git a/ShockOsc/MauiProgram.cs b/ShockOsc/MauiProgram.cs index 7ab4e45..fb1dbae 100644 --- a/ShockOsc/MauiProgram.cs +++ b/ShockOsc/MauiProgram.cs @@ -2,13 +2,17 @@ using Microsoft.Maui.LifecycleEvents; using OpenShock.ShockOsc.Config; using MauiApp = OpenShock.ShockOsc.Ui.MauiApp; -using Microsoft.UI; +using OpenShock.ShockOsc.Services.Pipes; +#if WINDOWS +using OpenShock.ShockOsc.Platforms.Windows; +#endif namespace OpenShock.ShockOsc; public static class MauiProgram { private static ShockOscConfig? _config; + private static PipeServerService? _pipeServerService; public static Microsoft.Maui.Hosting.MauiApp CreateMauiApp() { @@ -17,20 +21,28 @@ public static Microsoft.Maui.Hosting.MauiApp CreateMauiApp() // <---- Services ----> builder.Services.AddShockOscServices(); - + #if WINDOWS builder.Services.AddWindowsServices(); - + builder.ConfigureLifecycleEvents(lifecycleBuilder => { lifecycleBuilder.AddWindows(windowsLifecycleBuilder => { windowsLifecycleBuilder.OnWindowCreated(window => { - var handle = WinRT.Interop.WindowNative.GetWindowHandle(window); - var id = Win32Interop.GetWindowIdFromWindow(handle); - var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(id); - + var appWindow = WindowUtils.GetAppWindow(window); + + if (_pipeServerService != null) + { + _pipeServerService.OnMessageReceived += () => + { + appWindow.ShowOnTop(); + + return Task.CompletedTask; + }; + } + //When user execute the closing method, we can push a display alert. If user click Yes, close this application, if click the cancel, display alert will dismiss. appWindow.Closing += async (s, e) => { @@ -41,32 +53,35 @@ public static Microsoft.Maui.Hosting.MauiApp CreateMauiApp() appWindow.Hide(); return; } + + if(Application.Current == null) return; - var result = await Application.Current.MainPage.DisplayAlert( + var result = await Application.Current.MainPage!.DisplayAlert( "Close?", "Do you want to close ShockOSC?", "Yes", "Cancel"); - if (result) Application.Current.Quit(); + if (result) Application.Current?.Quit(); }; }); }); }); #endif - + // <---- App ----> builder .UseMauiApp() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }); - + var app = builder.Build(); - + _config = app.Services.GetRequiredService().Config; + _pipeServerService = app.Services.GetRequiredService(); app.Services.StartShockOscServices(false); - + return app; } } diff --git a/ShockOsc/Platforms/Windows/PipeHelper.cs b/ShockOsc/Platforms/Windows/PipeHelper.cs index 4cf5606..0da0ede 100644 --- a/ShockOsc/Platforms/Windows/PipeHelper.cs +++ b/ShockOsc/Platforms/Windows/PipeHelper.cs @@ -1,6 +1,8 @@ -using System.Collections; +#if WINDOWS +using System.Collections; -namespace OpenShock.ShockOsc; +// ReSharper disable once CheckNamespace +namespace OpenShock.ShockOsc.Platforms.Windows; public static class PipeHelper { @@ -29,4 +31,5 @@ bool MoveNextSafe(IEnumerator enumerator) { } } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/ShockOsc/Platforms/Windows/WindowUtils.cs b/ShockOsc/Platforms/Windows/WindowUtils.cs new file mode 100644 index 0000000..42173c7 --- /dev/null +++ b/ShockOsc/Platforms/Windows/WindowUtils.cs @@ -0,0 +1,26 @@ +#if WINDOWS +using Microsoft.UI; +using Microsoft.UI.Windowing; + +// ReSharper disable once CheckNamespace +namespace OpenShock.ShockOsc.Platforms.Windows; + +public static class WindowUtils +{ + public static void ShowOnTop(this AppWindow appWindow) + { + appWindow.Show(); + + if (appWindow.Presenter is not OverlappedPresenter presenter) return; + presenter.IsAlwaysOnTop = true; + presenter.IsAlwaysOnTop = false; + } + + public static AppWindow GetAppWindow(object window) + { + var handle = WinRT.Interop.WindowNative.GetWindowHandle(window); + var id = Win32Interop.GetWindowIdFromWindow(handle); + return AppWindow.GetFromWindowId(id); + } +} +#endif \ No newline at end of file diff --git a/ShockOsc/Platforms/Windows/WindowsEntryPoint.cs b/ShockOsc/Platforms/Windows/WindowsEntryPoint.cs index 690cfeb..f0f7cad 100644 --- a/ShockOsc/Platforms/Windows/WindowsEntryPoint.cs +++ b/ShockOsc/Platforms/Windows/WindowsEntryPoint.cs @@ -1,12 +1,10 @@ #if WINDOWS -using System.Diagnostics; using System.IO.Pipes; using System.Runtime.InteropServices; using System.Text.Json; using CommandLine; using Microsoft.Extensions.Hosting; using Microsoft.UI.Dispatching; -using Microsoft.Windows.AppLifecycle; using OpenShock.ShockOsc.Cli; using OpenShock.ShockOsc.Cli.Uri; using OpenShock.ShockOsc.Services; @@ -16,6 +14,7 @@ using Application = Microsoft.UI.Xaml.Application; using UriParser = OpenShock.ShockOsc.Cli.Uri.UriParser; +// ReSharper disable once CheckNamespace namespace OpenShock.ShockOsc.Platforms.Windows; public static class WindowsEntryPoint @@ -54,7 +53,7 @@ private static void Start(CliOptions config) } const string pipeName = @"\\.\pipe\OpenShock.ShockOSC"; - + if (PipeHelper.EnumeratePipes().Any(x => x.Equals(pipeName, StringComparison.InvariantCultureIgnoreCase))) { // TODO: Refactor this @@ -63,19 +62,22 @@ private static void Start(CliOptions config) using var pipeClientStream = new NamedPipeClientStream(".", "OpenShock.ShockOsc", PipeDirection.Out); pipeClientStream.Connect(500); + var parsedUri = UriParser.Parse(config.Uri); + using var writer = new StreamWriter(pipeClientStream); writer.AutoFlush = true; - var parsedUri = UriParser.Parse(config.Uri); - - if (parsedUri.Type == UriParameterType.Token) + var pipeMessage = parsedUri.Type switch { - writer.WriteLine(JsonSerializer.Serialize(new PipeMessage + UriParameterType.Show => new PipeMessage { Type = PipeMessageType.Show }, + UriParameterType.Token => new PipeMessage { - Type = PipeMessageType.Token, - Data = string.Join('/', parsedUri.Arguments) - })); - } + Type = PipeMessageType.Token, Data = string.Join('/', parsedUri.Arguments) + }, + _ => null + }; + + if (pipeMessage != null) writer.WriteLine(JsonSerializer.Serialize(pipeMessage)); return; } diff --git a/ShockOsc/Platforms/Windows/WindowsServices.cs b/ShockOsc/Platforms/Windows/WindowsServices.cs index e1abe29..5380771 100644 --- a/ShockOsc/Platforms/Windows/WindowsServices.cs +++ b/ShockOsc/Platforms/Windows/WindowsServices.cs @@ -1,7 +1,8 @@ #if WINDOWS using OpenShock.ShockOsc.Services; -namespace OpenShock.ShockOsc; +// ReSharper disable once CheckNamespace +namespace OpenShock.ShockOsc.Platforms.Windows; public static class WindowsServices { diff --git a/ShockOsc/Platforms/Windows/WindowsTrayService.cs b/ShockOsc/Platforms/Windows/WindowsTrayService.cs index 091c6e7..0a59cb1 100644 --- a/ShockOsc/Platforms/Windows/WindowsTrayService.cs +++ b/ShockOsc/Platforms/Windows/WindowsTrayService.cs @@ -2,20 +2,22 @@ using System.Drawing; using System.Windows.Forms; -using Microsoft.UI; -using Microsoft.UI.Windowing; using OpenShock.SDK.CSharp.Hub; using OpenShock.ShockOsc.Services; using Application = Microsoft.Maui.Controls.Application; -using Color = System.Drawing.Color; using Image = System.Drawing.Image; -namespace OpenShock.ShockOsc; +// ReSharper disable once CheckNamespace +namespace OpenShock.ShockOsc.Platforms.Windows; public class WindowsTrayService : ITrayService { private readonly OpenShockHubClient _apiHubClient; + /// + /// Windows Tray Service + /// + /// public WindowsTrayService(OpenShockHubClient apiHubClient) { _apiHubClient = apiHubClient; @@ -61,7 +63,7 @@ public void Initialize() tray.Visible = true; } - private void Restart(object? sender, EventArgs e) + private static void Restart(object? sender, EventArgs e) { Application.Current?.Quit(); } @@ -73,18 +75,10 @@ private static void OnMainClick(object? sender, EventArgs eventArgs) var window = Application.Current?.Windows[0]; var nativeWindow = window?.Handler?.PlatformView; if (nativeWindow == null) return; - - var windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(nativeWindow); - var windowId = Win32Interop.GetWindowIdFromWindow(windowHandle); - var appWindow = AppWindow.GetFromWindowId(windowId); - appWindow.Show(); - - if (appWindow.Presenter is OverlappedPresenter presenter) - { - presenter.IsAlwaysOnTop = true; - presenter.IsAlwaysOnTop = false; - } + var appWindow = WindowUtils.GetAppWindow(nativeWindow); + + appWindow.ShowOnTop(); } private static void OnQuitClick(object? sender, EventArgs eventArgs) diff --git a/ShockOsc/Services/Pipes/PipeMessageType.cs b/ShockOsc/Services/Pipes/PipeMessageType.cs index e99a79b..5e2ba13 100644 --- a/ShockOsc/Services/Pipes/PipeMessageType.cs +++ b/ShockOsc/Services/Pipes/PipeMessageType.cs @@ -2,5 +2,6 @@ public enum PipeMessageType { + Show, Token } \ No newline at end of file diff --git a/ShockOsc/Services/Pipes/PipeServerService.cs b/ShockOsc/Services/Pipes/PipeServerService.cs index a5b5935..eae0192 100644 --- a/ShockOsc/Services/Pipes/PipeServerService.cs +++ b/ShockOsc/Services/Pipes/PipeServerService.cs @@ -66,11 +66,11 @@ private async Task ServerLoop() case PipeMessageType.Token: Token = jsonObj.Data?.ToString(); break; + case PipeMessageType.Show: + default: + break; } - { - - } - + await OnMessageReceived.Raise(); _logger.LogInformation("[{Id}], Received pipe message of type: {Type}", id, jsonObj.Type); } diff --git a/ShockOsc/Services/ShockOsc.cs b/ShockOsc/Services/ShockOsc.cs index edfe912..6c00e69 100644 --- a/ShockOsc/Services/ShockOsc.cs +++ b/ShockOsc/Services/ShockOsc.cs @@ -70,14 +70,17 @@ public ShockOsc(ILogger logger, _dataLayer = dataLayer; _oscHandler = oscHandler; _liveControlManager = liveControlManager; - - - OnGroupsChanged += SetupGroups; + + OnGroupsChanged += () => + { + SetupGroups(); + return Task.CompletedTask; + }; oscQueryServer.FoundVrcClient += FoundVrcClient; oscQueryServer.ParameterUpdate += OnAvatarChange; - - SetupGroups().Wait(); + + SetupGroups(); if (!_configManager.Config.Osc.OscQuery) { @@ -87,7 +90,7 @@ public ShockOsc(ILogger logger, _logger.LogInformation("Started ShockOsc.cs"); } - private async Task SetupGroups() + private void SetupGroups() { _dataLayer.ProgramGroups.Clear(); _dataLayer.ProgramGroups[Guid.Empty] = new ProgramGroup(Guid.Empty, "_All", _oscClient, null); @@ -101,7 +104,7 @@ private void OnParamChange(bool shockOscParam) OnParamsChange?.Invoke(shockOscParam); } - public async Task FoundVrcClient(IPEndPoint? oscClient) + private async Task FoundVrcClient(IPEndPoint? oscClient) { _logger.LogInformation("Found VRC client"); // stop tasks @@ -129,7 +132,7 @@ public async Task FoundVrcClient(IPEndPoint? oscClient) _logger.LogInformation("Ready"); OsTask.Run(_underscoreConfig.SendUpdateForAll); - _oscClient.SendChatboxMessage($"{_configManager.Config.Chatbox.Prefix} Game Connected"); + await _oscClient.SendChatboxMessage($"{_configManager.Config.Chatbox.Prefix} Game Connected"); } public async Task OnAvatarChange(Dictionary parameters, string avatarId) diff --git a/ShockOsc/Ui/Pages/Authentication/LoginPart.razor b/ShockOsc/Ui/Pages/Authentication/LoginPart.razor index b524a31..821ad64 100644 --- a/ShockOsc/Ui/Pages/Authentication/LoginPart.razor +++ b/ShockOsc/Ui/Pages/Authentication/LoginPart.razor @@ -79,7 +79,7 @@ private string? _server = null; - [Parameter] public Func ProceedAuthenticated { get; set; } + [Parameter] public required Func ProceedAuthenticated { get; set; } public async Task Login() { @@ -141,7 +141,8 @@ BackendServer.Production => _productionServer, BackendServer.Staging => _stagingServer, BackendServer.Custom => _customServerUri, - }; + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + } ?? throw new InvalidOperationException(); } private struct WrongSchema; diff --git a/ShockOsc/Ui/Utils/DebouncedSlider.razor.cs b/ShockOsc/Ui/Utils/DebouncedSlider.razor.cs index 678454f..0a900a0 100644 --- a/ShockOsc/Ui/Utils/DebouncedSlider.razor.cs +++ b/ShockOsc/Ui/Utils/DebouncedSlider.razor.cs @@ -37,7 +37,9 @@ protected override void OnInitialized() private T _sliderValue = default!; [Parameter] +#pragma warning disable BL0007 public T SliderValue +#pragma warning restore BL0007 { get => _sliderValue; set