Skip to content

Commit 1b066a5

Browse files
committed
Improve code quality
1 parent dda008f commit 1b066a5

File tree

2 files changed

+108
-127
lines changed

2 files changed

+108
-127
lines changed

Flow.Launcher/App.xaml.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ namespace Flow.Launcher
2828
public partial class App : IDisposable, ISingleInstanceApp
2929
{
3030
public static IPublicAPI API { get; private set; }
31-
private const string Unique = "Flow.Launcher_Unique_Application_Mutex";
3231
private static bool _disposed;
3332
private readonly Settings _settings;
3433

@@ -99,7 +98,7 @@ private static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
9998
[STAThread]
10099
public static void Main()
101100
{
102-
if (SingleInstance<App>.InitializeAsFirstInstance(Unique))
101+
if (SingleInstance<App>.InitializeAsFirstInstance())
103102
{
104103
using var application = new App();
105104
application.InitializeComponent();

Flow.Launcher/Helper/SingleInstance.cs

Lines changed: 107 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -6,155 +6,137 @@
66

77
// http://blogs.microsoft.co.il/arik/2010/05/28/wpf-single-instance-application/
88
// modified to allow single instace restart
9-
namespace Flow.Launcher.Helper
9+
namespace Flow.Launcher.Helper;
10+
11+
public interface ISingleInstanceApp
1012
{
11-
public interface ISingleInstanceApp
12-
{
13-
void OnSecondAppStarted();
14-
}
13+
void OnSecondAppStarted();
14+
}
15+
16+
/// <summary>
17+
/// This class checks to make sure that only one instance of
18+
/// this application is running at a time.
19+
/// </summary>
20+
/// <remarks>
21+
/// Note: this class should be used with some caution, because it does no
22+
/// security checking. For example, if one instance of an app that uses this class
23+
/// is running as Administrator, any other instance, even if it is not
24+
/// running as Administrator, can activate it with command line arguments.
25+
/// For most apps, this will not be much of an issue.
26+
/// </remarks>
27+
public static class SingleInstance<TApplication> where TApplication: Application, ISingleInstanceApp
28+
{
29+
#region Private Fields
1530

1631
/// <summary>
17-
/// This class checks to make sure that only one instance of
18-
/// this application is running at a time.
32+
/// String delimiter used in channel names.
1933
/// </summary>
20-
/// <remarks>
21-
/// Note: this class should be used with some caution, because it does no
22-
/// security checking. For example, if one instance of an app that uses this class
23-
/// is running as Administrator, any other instance, even if it is not
24-
/// running as Administrator, can activate it with command line arguments.
25-
/// For most apps, this will not be much of an issue.
26-
/// </remarks>
27-
public static class SingleInstance<TApplication>
28-
where TApplication: Application , ISingleInstanceApp
29-
30-
{
31-
#region Private Fields
34+
private const string Delimiter = ":";
3235

33-
/// <summary>
34-
/// String delimiter used in channel names.
35-
/// </summary>
36-
private const string Delimiter = ":";
36+
/// <summary>
37+
/// Suffix to the channel name.
38+
/// </summary>
39+
private const string ChannelNameSuffix = "SingeInstanceIPCChannel";
40+
private const string InstanceMutexName = "Flow.Launcher_Unique_Application_Mutex";
41+
42+
/// <summary>
43+
/// Application mutex.
44+
/// </summary>
45+
internal static Mutex SingleInstanceMutex { get; set; }
3746

38-
/// <summary>
39-
/// Suffix to the channel name.
40-
/// </summary>
41-
private const string ChannelNameSuffix = "SingeInstanceIPCChannel";
47+
#endregion
4248

43-
/// <summary>
44-
/// Application mutex.
45-
/// </summary>
46-
internal static Mutex singleInstanceMutex;
49+
#region Public Methods
4750

48-
#endregion
51+
/// <summary>
52+
/// Checks if the instance of the application attempting to start is the first instance.
53+
/// If not, activates the first instance.
54+
/// </summary>
55+
/// <returns>True if this is the first instance of the application.</returns>
56+
public static bool InitializeAsFirstInstance()
57+
{
58+
// Build unique application Id and the IPC channel name.
59+
string applicationIdentifier = InstanceMutexName + Environment.UserName;
4960

50-
#region Public Methods
61+
string channelName = string.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);
5162

52-
/// <summary>
53-
/// Checks if the instance of the application attempting to start is the first instance.
54-
/// If not, activates the first instance.
55-
/// </summary>
56-
/// <returns>True if this is the first instance of the application.</returns>
57-
public static bool InitializeAsFirstInstance( string uniqueName )
63+
// Create mutex based on unique application Id to check if this is the first instance of the application.
64+
SingleInstanceMutex = new Mutex(true, applicationIdentifier, out var firstInstance);
65+
if (firstInstance)
5866
{
59-
// Build unique application Id and the IPC channel name.
60-
string applicationIdentifier = uniqueName + Environment.UserName;
61-
62-
string channelName = String.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);
63-
64-
// Create mutex based on unique application Id to check if this is the first instance of the application.
65-
bool firstInstance;
66-
singleInstanceMutex = new Mutex(true, applicationIdentifier, out firstInstance);
67-
if (firstInstance)
68-
{
69-
_ = CreateRemoteService(channelName);
70-
return true;
71-
}
72-
else
73-
{
74-
_ = SignalFirstInstance(channelName);
75-
return false;
76-
}
67+
_ = CreateRemoteServiceAsync(channelName);
68+
return true;
7769
}
78-
79-
/// <summary>
80-
/// Cleans up single-instance code, clearing shared resources, mutexes, etc.
81-
/// </summary>
82-
public static void Cleanup()
70+
else
8371
{
84-
singleInstanceMutex?.ReleaseMutex();
72+
_ = SignalFirstInstanceAsync(channelName);
73+
return false;
8574
}
75+
}
8676

87-
#endregion
77+
/// <summary>
78+
/// Cleans up single-instance code, clearing shared resources, mutexes, etc.
79+
/// </summary>
80+
public static void Cleanup()
81+
{
82+
SingleInstanceMutex?.ReleaseMutex();
83+
}
8884

89-
#region Private Methods
85+
#endregion
9086

91-
/// <summary>
92-
/// Creates a remote server pipe for communication.
93-
/// Once receives signal from client, will activate first instance.
94-
/// </summary>
95-
/// <param name="channelName">Application's IPC channel name.</param>
96-
private static async Task CreateRemoteService(string channelName)
97-
{
98-
using (NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In))
99-
{
100-
while(true)
101-
{
102-
// Wait for connection to the pipe
103-
await pipeServer.WaitForConnectionAsync();
104-
if (Application.Current != null)
105-
{
106-
// Do an asynchronous call to ActivateFirstInstance function
107-
Application.Current.Dispatcher.Invoke(ActivateFirstInstance);
108-
}
109-
// Disconect client
110-
pipeServer.Disconnect();
111-
}
112-
}
113-
}
87+
#region Private Methods
11488

115-
/// <summary>
116-
/// Creates a client pipe and sends a signal to server to launch first instance
117-
/// </summary>
118-
/// <param name="channelName">Application's IPC channel name.</param>
119-
/// <param name="args">
120-
/// Command line arguments for the second instance, passed to the first instance to take appropriate action.
121-
/// </param>
122-
private static async Task SignalFirstInstance(string channelName)
89+
/// <summary>
90+
/// Creates a remote server pipe for communication.
91+
/// Once receives signal from client, will activate first instance.
92+
/// </summary>
93+
/// <param name="channelName">Application's IPC channel name.</param>
94+
private static async Task CreateRemoteServiceAsync(string channelName)
95+
{
96+
using NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In);
97+
while (true)
12398
{
124-
// Create a client pipe connected to server
125-
using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out))
126-
{
127-
// Connect to the available pipe
128-
await pipeClient.ConnectAsync(0);
129-
}
130-
}
99+
// Wait for connection to the pipe
100+
await pipeServer.WaitForConnectionAsync();
131101

132-
/// <summary>
133-
/// Callback for activating first instance of the application.
134-
/// </summary>
135-
/// <param name="arg">Callback argument.</param>
136-
/// <returns>Always null.</returns>
137-
private static object ActivateFirstInstanceCallback(object o)
138-
{
139-
ActivateFirstInstance();
140-
return null;
102+
// Do an asynchronous call to ActivateFirstInstance function
103+
Application.Current?.Dispatcher.Invoke(ActivateFirstInstance);
104+
105+
// Disconect client
106+
pipeServer.Disconnect();
141107
}
108+
}
142109

143-
/// <summary>
144-
/// Activates the first instance of the application with arguments from a second instance.
145-
/// </summary>
146-
/// <param name="args">List of arguments to supply the first instance of the application.</param>
147-
private static void ActivateFirstInstance()
148-
{
149-
// Set main window state and process command line args
150-
if (Application.Current == null)
151-
{
152-
return;
153-
}
110+
/// <summary>
111+
/// Creates a client pipe and sends a signal to server to launch first instance
112+
/// </summary>
113+
/// <param name="channelName">Application's IPC channel name.</param>
114+
/// <param name="args">
115+
/// Command line arguments for the second instance, passed to the first instance to take appropriate action.
116+
/// </param>
117+
private static async Task SignalFirstInstanceAsync(string channelName)
118+
{
119+
// Create a client pipe connected to server
120+
using NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out);
154121

155-
((TApplication)Application.Current).OnSecondAppStarted();
122+
// Connect to the available pipe
123+
await pipeClient.ConnectAsync(0);
124+
}
125+
126+
/// <summary>
127+
/// Activates the first instance of the application with arguments from a second instance.
128+
/// </summary>
129+
/// <param name="args">List of arguments to supply the first instance of the application.</param>
130+
private static void ActivateFirstInstance()
131+
{
132+
// Set main window state and process command line args
133+
if (Application.Current == null)
134+
{
135+
return;
156136
}
157137

158-
#endregion
138+
((TApplication)Application.Current).OnSecondAppStarted();
159139
}
140+
141+
#endregion
160142
}

0 commit comments

Comments
 (0)