I am working on an WPF application to monitor my activities on my computer. I use Process.GetProcesses()
and some filtering to get the processes I am interested in
I eventually found a way to do this, so I am going answer my own question so maybe someone in the future with the same problem could find it useful.
This is the class with the WinApiFunctions:
public class WinAPIFunctions
{
//Used to get Handle for Foreground Window
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr GetForegroundWindow();
//Used to get ID of any Window
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
public delegate bool WindowEnumProc(IntPtr hwnd, IntPtr lparam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc callback, IntPtr lParam);
public static int GetWindowProcessId(IntPtr hwnd)
{
int pid;
GetWindowThreadProcessId(hwnd, out pid);
return pid;
}
public static IntPtr GetforegroundWindow()
{
return GetForegroundWindow();
}
}
And this is the class I used to test if it would work. I used it in a simple console program that just writes out the name of the process that has current focus:
class FindHostedProcess
{
public Timer MyTimer { get; set; }
private Process _realProcess;
public FindHostedProcess()
{
MyTimer = new Timer(TimerCallback, null, 0, 1000);
Console.ReadKey();
}
private void TimerCallback(object state)
{
var foregroundProcess = Process.GetProcessById(WinAPIFunctions.GetWindowProcessId(WinAPIFunctions.GetforegroundWindow()));
if (foregroundProcess.ProcessName == "ApplicationFrameHost")
{
foregroundProcess = GetRealProcess(foregroundProcess);
}
Console.WriteLine(foregroundProcess.ProcessName);
}
private Process GetRealProcess(Process foregroundProcess)
{
WinAPIFunctions.EnumChildWindows(foregroundProcess.MainWindowHandle, ChildWindowCallback, IntPtr.Zero);
return _realProcess;
}
private bool ChildWindowCallback(IntPtr hwnd, IntPtr lparam)
{
var process = Process.GetProcessById(WinAPIFunctions.GetWindowProcessId(hwnd));
if (process.ProcessName != "ApplicationFrameHost")
{
_realProcess = process;
}
return true;
}
}
About the same code from Chris Johnsson except that I'm not using Process because it doesn't work very well on the UWP app.
Code for start:
new Timer(TimerCallback, null, 0, 1000);
void TimerCallback(object state)
{
var process = new ProcessUtils.FindHostedProcess().Process;
string name = string.Empty;
if (process.IsPackaged)
{
var apps = process.GetAppDiagnosticInfos();
if (apps.Count > 0)
name = apps.First().AppInfo.DisplayInfo.DisplayName;
else
name = System.IO.Path.GetFileNameWithoutExtension(process.ExecutableFileName);
}
else
name = System.IO.Path.GetFileNameWithoutExtension(process.ExecutableFileName);
Debug.WriteLine(name);
}
And the FindHostedProcess class where I use ProcessDiagnosticInfo instead of Process
public class FindHostedProcess
{
public ProcessDiagnosticInfo Process { get; private set; }
public FindHostedProcess()
{
var foregroundProcessID = WinAPIFunctions.GetforegroundWindow();
Process = ProcessDiagnosticInfo.TryGetForProcessId((uint)WinAPIFunctions.GetWindowProcessId(foregroundProcessID));
// Get real process
if (Process.ExecutableFileName == "ApplicationFrameHost.exe")
WinAPIFunctions.EnumChildWindows(foregroundProcessID, ChildWindowCallback, IntPtr.Zero);
}
private bool ChildWindowCallback(IntPtr hwnd, IntPtr lparam)
{
var process = ProcessDiagnosticInfo.TryGetForProcessId((uint)WinAPIFunctions.GetWindowProcessId(hwnd));
if (process.ExecutableFileName != "ApplicationFrameHost.exe")
Process = process;
return true;
}
}
Finally reuse Chris Johnson's WinAPIFunctions class.
Chris, there is the alternative way which I have discovered trying to apply your solution to a related problem. While trying to analyse how the ApplicationFrameHost.exe
-related stuff worked, I have stumbled upon the documented way of getting the true foreground window / thread and its process by passing 0
instead of the actual thread ID to GetGUIThreadInfo.
It might not fully work for the edge-case scenarios of your problem, but I felt that this might be a useful contribution for people of the future who might face the same problems ;-)
Here is the example of how this can be applied (pseudo C++'ish code):
GUITHREADINFO gti = { sizeof(GUITHREADINFO) };
GetGUIThreadInfo(0, >i); // <- note the `0`
DWORD processId = 0;
GetWindowThreadProcessId(gti.hwndFocus, &processId);
const auto procName = Util::GetProcessName(processId);
It solved my problem (obtaining the actual keyboard layout + finding the real foreground window) for all more-or-less common apps I have tested it against.
Basically, the answer you provided will also fail if the active window is in full screen mode,
for example if you have Skype app opened along with Microsoft Remote Desktop, which is the
active one and on full screen mode, EnumChildWindows
would return SkypeApp not RDPClient.
and to get this fixed, you should follow the workaround suggested by Ivanmoskalev,
check this out:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetGUIThreadInfo(uint idThread, ref GUITHREADINFO lpgui);
public static IntPtr getThreadWindowHandle(uint dwThreadId)
{
IntPtr hWnd;
// Get Window Handle and title from Thread
var guiThreadInfo = new GUITHREADINFO();
guiThreadInfo.cbSize = Marshal.SizeOf(guiThreadInfo);
GetGUIThreadInfo(dwThreadId, ref guiThreadInfo);
hWnd = guiThreadInfo.hwndFocus;
//some times while changing the focus between different windows, it returns Zero so we would return the Active window in that case
if (hWnd == IntPtr.Zero)
{
hWnd = guiThreadInfo.hwndActive;
}
return hWnd;
}
[DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr windowHandle, out int processId);
static void Main(string[] args)
{
var current = getThreadWindowHandle(0);
int processId = 0;
GetWindowThreadProcessId(current, out processId);
var foregroundProcess = GetActiveProcess(processId);
}
private static Process GetActiveProcess(int activeWindowProcessId)
{
Process foregroundProcess = null;
try
{
foregroundProcess = Process.GetProcessById(activeWindowProcessId);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
if (string.IsNullOrWhiteSpace(GetProcessNameSafe(foregroundProcess)))
{
var msg = "Process name is empty.";
Console.WriteLine(msg);
}
return foregroundProcess;
}