/*
* Copyright (c) 2025 Proton AG
*
* This file is part of ProtonVPN.
*
* ProtonVPN is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonVPN is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonVPN. If not, see .
*/
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Windows.AppLifecycle;
namespace ProtonVPN.Client.Services.Bootstrapping.Helpers;
///
/// Documentation on how to create single-instanced application
/// and why we should redirect protocol activation to the running instance can be found here:
/// https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/applifecycle/applifecycle-single-instance
///
public static class AppInstanceHelper
{
private const int SW_SHOW = 5;
private static IntPtr _redirectEventHandle = IntPtr.Zero;
private delegate bool EnumWindowsProc(nint hWnd, nint lParam);
public static bool IsRunningAsAdmin()
{
try
{
using WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
catch
{
return false;
}
}
public static void RedirectActivation()
{
AppInstance? instance = AppInstance.GetInstances().FirstOrDefault(i => !i.IsCurrent);
if (instance is not null)
{
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
RedirectActivationTo(instance, args);
}
}
private static void RedirectActivationTo(AppInstance keyInstance, AppActivationArguments args)
{
_redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, string.Empty);
Task.Run(() =>
{
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
SetEvent(_redirectEventHandle);
});
uint CWMO_DEFAULT = 0;
uint INFINITE = 0xFFFFFFFF;
_ = CoWaitForMultipleObjects(
CWMO_DEFAULT, INFINITE, 1,
[_redirectEventHandle], out uint handleIndex);
// Bring the window to the foreground
BringToForeground();
}
private static void BringToForeground()
{
Process currentProcess = Process.GetCurrentProcess();
List processes = Process.GetProcessesByName(currentProcess.ProcessName)
.Where(p => p.Id != currentProcess.Id).ToList();
foreach (Process process in processes)
{
BringProcessMainWindowToForeground(process);
}
}
private static void BringProcessMainWindowToForeground(Process process)
{
try
{
nint mainWindowHandle = GetProcessMainWindowHandle(process);
if (mainWindowHandle != nint.Zero)
{
ShowWindow(mainWindowHandle, SW_SHOW);
SwitchToThisWindow(mainWindowHandle, true);
}
}
catch
{ }
}
private static nint GetProcessMainWindowHandle(Process process)
{
return process.MainWindowHandle == nint.Zero
? GetHiddenMainWindowHandleByProcessId(process.Id)
: process.MainWindowHandle;
}
private static nint GetHiddenMainWindowHandleByProcessId(int processId)
{
nint mainWindowHandle = nint.Zero;
EnumWindows((hWnd, lParam) =>
{
GetWindowThreadProcessId(hWnd, out uint windowProcessId);
if (windowProcessId == processId)
{
int length = GetWindowTextLength(hWnd);
nint buffer = Marshal.AllocHGlobal(length + 1);
GetWindowText(hWnd, buffer, length + 1);
string? title = Marshal.PtrToStringUni(buffer);
Marshal.FreeHGlobal(buffer);
if (title is not null &&
title.Equals(App.APPLICATION_NAME, StringComparison.InvariantCulture))
{
mainWindowHandle = hWnd;
return false;
}
}
return true;
}, nint.Zero);
return mainWindowHandle;
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
[DllImport("kernel32.dll")]
private static extern bool SetEvent(IntPtr hEvent);
[DllImport("ole32.dll")]
private static extern uint CoWaitForMultipleObjects(uint dwFlags, uint dwMilliseconds, ulong nHandles, IntPtr[] pHandles, out uint dwIndex);
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsProc enumProc, nint lParam);
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int GetWindowTextLength(nint hWnd);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int GetWindowText(nint hWnd, nint lpString, int nMaxCount);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ShowWindow(nint hWnd, int nCmdShow);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SwitchToThisWindow(nint hWnd, bool fAltTab);
}