mirror of
https://github.com/cemu-project/DS4Windows.git
synced 2024-12-27 09:01:48 +01:00
252 lines
12 KiB
C#
252 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
using System.Text;
|
|
using System.Diagnostics; // StopWatch
|
|
using System.Threading; // Sleep
|
|
using System.Threading.Tasks;
|
|
using DS4Windows;
|
|
|
|
namespace DS4WinWPF
|
|
{
|
|
[SuppressUnmanagedCodeSecurity]
|
|
public class AutoProfileChecker
|
|
{
|
|
private AutoProfileHolder profileHolder;
|
|
private IntPtr prevForegroundWnd = IntPtr.Zero;
|
|
private uint prevForegroundProcessID;
|
|
private string prevForegroundProcessName = string.Empty;
|
|
private string prevForegroundWndTitleName = string.Empty;
|
|
private StringBuilder autoProfileCheckTextBuilder = new StringBuilder(1000);
|
|
private int autoProfileDebugLogLevel = 0;
|
|
private bool turnOffTemp;
|
|
private AutoProfileEntity tempAutoProfile;
|
|
private bool running;
|
|
|
|
public int AutoProfileDebugLogLevel { get => autoProfileDebugLogLevel; set => autoProfileDebugLogLevel = value; }
|
|
public bool Running { get => running; set => running = value; }
|
|
|
|
public delegate void ChangeServiceHandler(AutoProfileChecker sender, bool state);
|
|
public event ChangeServiceHandler RequestServiceChange;
|
|
|
|
public AutoProfileChecker(AutoProfileHolder holder)
|
|
{
|
|
profileHolder = holder;
|
|
}
|
|
|
|
public void Process()
|
|
{
|
|
string topProcessName, topWindowTitle;
|
|
bool turnOffDS4WinApp = false;
|
|
AutoProfileEntity matchedProfileEntity = null;
|
|
|
|
if (GetTopWindowName(out topProcessName, out topWindowTitle))
|
|
{
|
|
// Find a profile match based on autoprofile program path and wnd title list.
|
|
// The same program may set different profiles for each of the controllers, so we need an array of newProfileName[controllerIdx] values.
|
|
for (int i = 0, pathsLen = profileHolder.AutoProfileColl.Count; i < pathsLen; i++)
|
|
{
|
|
AutoProfileEntity tempEntity = profileHolder.AutoProfileColl[i];
|
|
if (tempEntity.IsMatch(topProcessName, topWindowTitle))
|
|
{
|
|
if (autoProfileDebugLogLevel > 0)
|
|
DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. Rule#{i + 1} Path={tempEntity.path} Title={tempEntity.title}", false);
|
|
|
|
// Matching autoprofile rule found
|
|
turnOffDS4WinApp = tempEntity.Turnoff;
|
|
matchedProfileEntity = tempEntity;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (matchedProfileEntity != null)
|
|
{
|
|
bool forceLoadProfile = false;
|
|
|
|
if (!turnOffDS4WinApp && turnOffTemp)
|
|
{
|
|
// DS4Win was temporarily turned off by another auto-profile rule. Turn DS4Win on before trying to load a new profile because otherwise the new profile won't do anything.
|
|
// Force load the profile when DS4Win service afer waking up DS4Win service to make sure that the new profile will be active.
|
|
turnOffTemp = false;
|
|
SetAndWaitServiceStatus(true);
|
|
forceLoadProfile = true;
|
|
}
|
|
|
|
// Program match found. Check if the new profile is different than current profile of the controller. Load the new profile only if it is not already loaded.
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
string tempname = matchedProfileEntity.ProfileNames[j];
|
|
if (tempname != string.Empty && tempname != "(none)")
|
|
{
|
|
if ((Global.useTempProfile[j] && tempname != Global.tempprofilename[j]) ||
|
|
(!Global.useTempProfile[j] && tempname != Global.ProfilePath[j]) ||
|
|
forceLoadProfile)
|
|
{
|
|
if (autoProfileDebugLogLevel > 0)
|
|
DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. LoadProfile Controller {j + 1}={tempname}", false);
|
|
|
|
Global.LoadTempProfile(j, tempname, true, Program.rootHub); // j is controller index, i is filename
|
|
//if (LaunchProgram[j] != string.Empty) Process.Start(LaunchProgram[j]);
|
|
}
|
|
else
|
|
{
|
|
if (autoProfileDebugLogLevel > 0)
|
|
DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. LoadProfile Controller {j + 1}={tempname} (already loaded)", false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (turnOffDS4WinApp)
|
|
{
|
|
turnOffTemp = true;
|
|
if (App.rootHub.running)
|
|
{
|
|
if (autoProfileDebugLogLevel > 0)
|
|
DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. Turning DS4Windows temporarily off", false);
|
|
|
|
SetAndWaitServiceStatus(false);
|
|
}
|
|
}
|
|
|
|
tempAutoProfile = matchedProfileEntity;
|
|
}
|
|
else if (tempAutoProfile != null)
|
|
{
|
|
if (turnOffTemp && DS4Windows.Global.AutoProfileRevertDefaultProfile)
|
|
{
|
|
turnOffTemp = false;
|
|
if (!App.rootHub.running)
|
|
{
|
|
if (autoProfileDebugLogLevel > 0)
|
|
DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. Turning DS4Windows on before reverting to default profile", false);
|
|
|
|
SetAndWaitServiceStatus(true);
|
|
}
|
|
}
|
|
|
|
tempAutoProfile = null;
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
if (Global.useTempProfile[j])
|
|
{
|
|
if (DS4Windows.Global.AutoProfileRevertDefaultProfile)
|
|
{
|
|
if (autoProfileDebugLogLevel > 0)
|
|
DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. Unknown process. Reverting to default profile. Controller {j + 1}={Global.ProfilePath[j]} (default)", false);
|
|
|
|
Global.LoadProfile(j, false, Program.rootHub);
|
|
}
|
|
else
|
|
{
|
|
if (autoProfileDebugLogLevel > 0)
|
|
DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. Unknown process. Existing profile left as active. Controller {j + 1}={Global.tempprofilename[j]}", false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool GetTopWindowName(out string topProcessName, out string topWndTitleName)
|
|
{
|
|
IntPtr hWnd = GetForegroundWindow();
|
|
if (hWnd == IntPtr.Zero)
|
|
{
|
|
// Top window unknown or cannot acquire a handle. Return FALSE and return unknown process and wndTitle values
|
|
prevForegroundWnd = IntPtr.Zero;
|
|
prevForegroundProcessID = 0;
|
|
topProcessName = topWndTitleName = String.Empty;
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// If this function was called from "auto-profile watcher timer" then check cached "previous hWnd handle". If the current hWnd is the same
|
|
// as during the previous check then return cached previous wnd and name values (ie. foreground app and window are assumed to be the same, so no need to re-query names).
|
|
// This should optimize the auto-profile timer check process and causes less burden to .NET GC collector because StringBuffer is not re-allocated every second.
|
|
//
|
|
// Note! hWnd handles may be re-cycled but not during the lifetime of the window. This "cache" optimization still works because when an old window is closed
|
|
// then foreground window changes to something else and the cached prevForgroundWnd variable is updated to store the new hWnd handle.
|
|
// It doesn't matter even when the previously cached handle is recycled by WinOS to represent some other window (it is no longer used as a cached value anyway).
|
|
//
|
|
if (hWnd == prevForegroundWnd)
|
|
{
|
|
// The active window is still the same. Return cached process and wndTitle values and FALSE to indicate caller that no changes since the last call of this method
|
|
topProcessName = prevForegroundProcessName;
|
|
topWndTitleName = prevForegroundWndTitleName;
|
|
return false;
|
|
}
|
|
|
|
prevForegroundWnd = hWnd;
|
|
|
|
IntPtr hProcess = IntPtr.Zero;
|
|
uint lpdwProcessId = 0;
|
|
GetWindowThreadProcessId(hWnd, out lpdwProcessId);
|
|
|
|
if (lpdwProcessId == prevForegroundProcessID)
|
|
{
|
|
topProcessName = prevForegroundProcessName;
|
|
}
|
|
else
|
|
{
|
|
prevForegroundProcessID = lpdwProcessId;
|
|
|
|
hProcess = OpenProcess(0x0410, false, lpdwProcessId);
|
|
if (hProcess != IntPtr.Zero) GetModuleFileNameEx(hProcess, IntPtr.Zero, autoProfileCheckTextBuilder, autoProfileCheckTextBuilder.Capacity);
|
|
else autoProfileCheckTextBuilder.Clear();
|
|
|
|
prevForegroundProcessName = topProcessName = autoProfileCheckTextBuilder.Replace('/', '\\').ToString().ToLower();
|
|
}
|
|
|
|
GetWindowText(hWnd, autoProfileCheckTextBuilder, autoProfileCheckTextBuilder.Capacity);
|
|
prevForegroundWndTitleName = topWndTitleName = autoProfileCheckTextBuilder.ToString().ToLower();
|
|
|
|
|
|
if (hProcess != IntPtr.Zero) CloseHandle(hProcess);
|
|
|
|
if (autoProfileDebugLogLevel > 0)
|
|
DS4Windows.AppLogger.LogToGui($"DEBUG: Auto-Profile. PID={lpdwProcessId} Path={topProcessName} | WND={hWnd} Title={topWndTitleName}", false);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void SetAndWaitServiceStatus(bool serviceRunningStatus)
|
|
{
|
|
// Start or Stop the service only if it is not already in the requested state
|
|
if (App.rootHub.running != serviceRunningStatus)
|
|
{
|
|
RequestServiceChange?.Invoke(this, serviceRunningStatus);
|
|
|
|
// Wait until DS4Win app service is running or stopped (as requested by serviceRunningStatus value) or timeout.
|
|
// LoadProfile call fails if a new profile is loaded while DS4Win service is still in stopped state (ie the loaded temp profile doesn't do anything).
|
|
Stopwatch sw = new Stopwatch();
|
|
sw.Start();
|
|
while (App.rootHub.running != serviceRunningStatus && sw.Elapsed.TotalSeconds < 10)
|
|
{
|
|
Thread.SpinWait(1000);
|
|
}
|
|
Thread.SpinWait(1000);
|
|
}
|
|
}
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr GetForegroundWindow();
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
|
|
|
[DllImport("kernel32.dll")]
|
|
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
|
|
|
|
[DllImport("kernel32.dll")]
|
|
private static extern bool CloseHandle(IntPtr handle);
|
|
|
|
[DllImport("psapi.dll")]
|
|
private static extern uint GetModuleFileNameEx(IntPtr hWnd, IntPtr hModule, StringBuilder lpFileName, int nSize);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
|
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nSize);
|
|
}
|
|
}
|