From 1c7bc2bb50241c832a7cf948f6f0da27bc748679 Mon Sep 17 00:00:00 2001 From: Chris Kaczor Date: Fri, 7 Apr 2023 10:38:17 -0400 Subject: [PATCH] Initial commit --- README.md | 15 ++++++ SingleInstance.cs | 67 +++++++++++++++++++++++++++ Wpf.Application.SingleInstance.csproj | 13 ++++++ Wpf.Application.SingleInstance.sln | 25 ++++++++++ 4 files changed, 120 insertions(+) create mode 100644 README.md create mode 100644 SingleInstance.cs create mode 100644 Wpf.Application.SingleInstance.csproj create mode 100644 Wpf.Application.SingleInstance.sln diff --git a/README.md b/README.md new file mode 100644 index 0000000..901c25a --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# ChrisKaczor.Wpf.Application.SingleInstance + +This library can be used to restrict an application to only a single instance and sends the command line of any newly started instance to the currently running instance. + +## Authors + +* **Chris Kaczor** - *Initial work* - https://github.com/ckaczor - https://chriskaczor.com + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. + +## Disclaimer + +This code is used by my various personal projects and may not be useful or work in all use cases. \ No newline at end of file diff --git a/SingleInstance.cs b/SingleInstance.cs new file mode 100644 index 0000000..f2b1515 --- /dev/null +++ b/SingleInstance.cs @@ -0,0 +1,67 @@ +using H.Pipes; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace ChrisKaczor.Wpf.Application +{ + public static class SingleInstance + { + public static event EventHandler MessageReceived = delegate { }; + + private static PipeServer? _pipeServer; + private static SynchronizationContext? _syncContext; + + public static async Task GetSingleInstanceHandleAsync(string applicationName) + { + // Create the mutex and see if it was newly created + var mutex = new Mutex(false, applicationName, out var createdNew); + + // Return the mutex if it was created + if (createdNew) return mutex; + + // Create a client to send to the other instance + await using var pipeClient = new PipeClient(applicationName); + + try + { + // Connect to the other instance + await pipeClient.ConnectAsync(new CancellationTokenSource(500).Token); + + // Send the command line to the other instance + await pipeClient.WriteAsync(Environment.CommandLine); + } + catch (Exception) + { + // Ignored + } + + return null; + } + + public static async Task StartAsync(string applicationName) + { + // Store the synchronization context of the current thread + _syncContext = SynchronizationContext.Current; + + _pipeServer = new PipeServer(applicationName); + + _pipeServer.MessageReceived += PipeServer_MessageReceived; + + await _pipeServer.StartAsync(); + } + + public static async Task Stop() + { + if (_pipeServer == null) return; + + await _pipeServer.StopAsync(); + } + + private static void PipeServer_MessageReceived(object? sender, H.Pipes.Args.ConnectionMessageEventArgs e) + { + // Fire the event on the original thread + _syncContext?.Send(_ => MessageReceived(null, e.Message!), null); + } + } +} \ No newline at end of file diff --git a/Wpf.Application.SingleInstance.csproj b/Wpf.Application.SingleInstance.csproj new file mode 100644 index 0000000..8d54485 --- /dev/null +++ b/Wpf.Application.SingleInstance.csproj @@ -0,0 +1,13 @@ + + + net7.0-windows + enable + true + ChrisKaczor.Wpf.Application + ChrisKaczor.Wpf.Application.SingleInstance + Chris Kaczor + + + + + diff --git a/Wpf.Application.SingleInstance.sln b/Wpf.Application.SingleInstance.sln new file mode 100644 index 0000000..a49250b --- /dev/null +++ b/Wpf.Application.SingleInstance.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33516.290 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wpf.Application.SingleInstance", "Wpf.Application.SingleInstance.csproj", "{6A5FBB13-4B83-4F7D-84CC-9474711100EA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6A5FBB13-4B83-4F7D-84CC-9474711100EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A5FBB13-4B83-4F7D-84CC-9474711100EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A5FBB13-4B83-4F7D-84CC-9474711100EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A5FBB13-4B83-4F7D-84CC-9474711100EA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9AF9F429-2093-4B89-96EB-C55EFBFD6EDD} + EndGlobalSection +EndGlobal