-
I'm trying to add a single instance mode to an Avalonia-based application. My main goal is the following. If a system tray icon is enabled in settings, then before creating an application, check if it is already running, open existing application and quit. So far, I've managed to check if an application is already running using [ I've also added a code that opens main window on click on the system tray icon. Is there any way to simulate a click on system tray icon to show existing instance? |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 1 reply
-
For bringing the already running app to the front, I use a named pipe. When the app starts and can get that mutex (so no other app is running), it starts a When the app starts a second time (and cannot get the mutex), it uses a The running app receives the "Show" command and brings itself to the foreground (or do something else like showing a window or focusing an existing window). |
Beta Was this translation helpful? Give feedback.
-
Upd |
Beta Was this translation helpful? Give feedback.
-
Windows: named mutex or file lock And universal option is to get running processes by name, and check if it's the first instance. But that approach is cursed, as requires more access to the user system than it should. NamedPipes is more or less stable approach to send messages between instances on Windows/Linux/macOS. Lightweight http/tcp server might be better though. |
Beta Was this translation helpful? Give feedback.
-
I'm use filelock and netty private static bool IsLock(out int port)
{
var name = RunDir + "lock";
port = -1;
if (File.Exists(name))
{
try
{
using var temp = File.Open(name, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch
{
using var temp = File.Open(name, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
byte[] temp1 = new byte[4];
temp.ReadExactly(temp1);
port = BitConverter.ToInt32(temp1);
return true;
}
}
return false;
}
public static void StartLock()
{
LaunchSocketUtils.Init().Wait();
string name = RunDir + "lock";
s_lock = File.Open(name, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
var data = BitConverter.GetBytes(LaunchSocketUtils.Port);
s_lock.Write(data);
s_lock.Flush();
} and netty public static class LaunchSocketUtils
{
public static readonly List<FrpCloudObj> Servers = [];
public static int Port { get; private set; }
private const int TypeGameMouseState = 1;
private const int TypeGameMotd = 2;
private const int TypeLaunchShow = 3;
private const int TypeLaunchStart = 4;
private static bool s_isRun;
private static IEventLoopGroup _bossGroup;
private static IEventLoopGroup _workerGroup;
private static ServerBootstrap _bootstrap;
private static IChannel _channel;
private static readonly List<IChannel> _channels = [];
private static List<int> PortIsUsed()
{
var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
var ipsTCP = ipGlobalProperties.GetActiveTcpListeners();
var ipsUDP = ipGlobalProperties.GetActiveUdpListeners();
var tcpConnInfoArray = ipGlobalProperties.GetActiveTcpConnections();
var allPorts = new List<int>();
foreach (var ep in ipsTCP) allPorts.Add(ep.Port);
foreach (var ep in ipsUDP) allPorts.Add(ep.Port);
foreach (var conn in tcpConnInfoArray) allPorts.Add(conn.LocalEndPoint.Port);
return allPorts;
}
private static int GetFirstAvailablePort()
{
try
{
var portUsed = PortIsUsed();
if (portUsed.Count > 5000)
{
return -1;
}
var random = new Random();
do
{
int temp = random.Next() % 65535;
if (!portUsed.Contains(temp))
{
return temp;
}
}
while (true);
}
catch
{
var random = new Random();
do
{
try
{
int port = random.Next(65535);
using var socket = new TcpListener(IPAddress.Any, port);
socket.Start();
socket.Stop();
return port;
}
catch
{
}
} while (true);
}
}
private static async Task<int> RunServerAsync()
{
if (_channel != null)
{
return (_channel.LocalAddress as IPEndPoint)!.Port;
}
_bossGroup = new MultithreadEventLoopGroup(1);
_workerGroup = new MultithreadEventLoopGroup();
try
{
_bootstrap = new();
_bootstrap.Group(_bossGroup, _workerGroup);
_bootstrap.Channel<TcpServerSocketChannel>();
_bootstrap
.ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
{
channel.Pipeline.AddLast("colormc", new GameServerHandler());
}));
int port = GetFirstAvailablePort();
_channel = await _bootstrap.BindAsync(IPAddress.Any, port);
return port;
}
catch (Exception e)
{
PathBinding.OpenFileWithExplorer(Logs.Crash("netty error", e));
return 0;
}
}
private static async void Stop()
{
await _channel.CloseAsync();
await Task.WhenAll(
_bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
_workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));
}
private static void SendMessage(IByteBuffer byteBuffer)
{
foreach (var item in _channels.ToArray())
{
try
{
if (item.IsWritable)
{
item.WriteAndFlushAsync(byteBuffer.RetainedDuplicate());
}
}
catch
{
}
}
}
public static async Task SendMessage(int port)
{
if (port <= 0)
{
return;
}
var group = new MultithreadEventLoopGroup();
try
{
var bootstrap = new Bootstrap();
bootstrap
.Group(group)
.Channel<TcpSocketChannel>()
.Option(ChannelOption.TcpNodelay, true)
.Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
}));
IChannel clientChannel = await bootstrap.ConnectAsync(IPAddress.Parse("127.0.0.1"), port);
if (BaseBinding.StartLaunch != null)
{
var buf = Unpooled.Buffer();
buf.WriteInt(TypeLaunchStart)
.WriteStringList(BaseBinding.StartLaunch);
await clientChannel.WriteAndFlushAsync(buf);
}
else
{
var buf = Unpooled.Buffer();
buf.WriteInt(TypeLaunchShow);
await clientChannel.WriteAndFlushAsync(buf);
}
await clientChannel.CloseAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
}
}
public static async Task Init()
{
Port = await RunServerAsync();
App.OnClose += App_OnClose;
s_isRun = true;
new Thread(Run).Start();
}
private static void Run()
{
while (s_isRun)
{
if (Servers.Count != 0)
{
foreach (var item in Servers.ToArray())
{
SendServerInfo(item);
}
}
Thread.Sleep(2000);
}
}
private static void App_OnClose()
{
Stop();
s_isRun = false;
}
private static string ReadString(this IByteBuffer buf)
{
int size = buf.ReadInt();
var datas = new byte[size];
buf.ReadBytes(datas);
return Encoding.UTF8.GetString(datas);
}
public static void Clear()
{
Servers.Clear();
}
public static void AddServerInfo(FrpCloudObj obj)
{
Servers.Add(obj);
}
private class GameServerHandler : ChannelHandlerAdapter
{
public override void ChannelActive(IChannelHandlerContext ctx)
{
_channels.Add(ctx.Channel);
}
public override void ChannelInactive(IChannelHandlerContext ctx)
{
_channels.Remove(ctx.Channel);
}
public override void ChannelRead(IChannelHandlerContext context, object message)
{
if (message is IByteBuffer buffer)
{
try
{
int type = buffer.ReadInt();
if (type == TypeGameMouseState)
{
string uuid = buffer.ReadString();
var value = buffer.ReadBoolean();
GameJoystick.SetMouse(uuid, value);
}
else if (type == TypeLaunchShow)
{
App.Show();
}
else if (type == TypeLaunchStart)
{
BaseBinding.Launch(buffer.ReadStringList());
}
}
catch
{
}
}
}
public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
{
context.CloseAsync();
}
}
private static string[] ReadStringList(this IByteBuffer buf)
{
int size = buf.ReadInt();
string[] temp = new string[size];
for (int a = 0; a < size; a++)
{
temp[a] = buf.ReadString();
}
return temp;
}
private static IByteBuffer WriteString(this IByteBuffer buf, string data)
{
var temp = Encoding.UTF8.GetBytes(data);
buf.WriteInt(temp.Length);
buf.WriteBytes(temp);
return buf;
}
private static IByteBuffer WriteStringList(this IByteBuffer buf, string[] data)
{
buf.WriteInt(data.Length);
foreach (var item in data)
{
buf.WriteString(item);
}
return buf;
}
} the main look like this var builder = BuildAvaloniaApp();
if (IsLock(out var port))
{
LaunchSocketUtils.SendMessage(port).Wait();
return;
}
builder.StartWithClassicDesktopLifetime(args); public App()
{
StartLock();
} It work in windows linux macos |
Beta Was this translation helpful? Give feedback.
Windows: named mutex or file lock
Linux: file lock only
macOS: neither of above work, but packaged macOS are guaranteed to be single instance
And universal option is to get running processes by name, and check if it's the first instance. But that approach is cursed, as requires more access to the user system than it should.
NamedPipes is more or less stable approach to send messages between instances on Windows/Linux/macOS. Lightweight http/tcp server might be better though.