Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# minimized windows not being returned by call to System.Diagnostics.Process.GetProcesses()

Tags:

c#

windows

user32

I'm attempting to find a minimized window and Show it.

The program can be downloaded from Samsung and it is titled "SideSync". To fully replicate my question you would need to install this and also have a Samsung phone to plug in to your computer.

Here is a screen shot of it fully configured and running:

enter image description here

Observe that there are two windows, A and B. I used a tool titled Microsoft Inspect to determine that the two program windows are normal windows. They do not have a child parent relationship. However, when I start SideSync, only Window A appears. I must then click "Phone Screen" and then Window B appears (in addition to Window A). This might a clue to solve this issue? We shall see.

Here are both windows as they appear in Microsoft Inspect:

enter image description here

Both windows have Window Titles. Using the code below I can retrieve the Process of the window (which is my objective).

Server code:

public static Process GetProcessByWindowTitle(string windowTitleContains)
{
    foreach (var windowProcess in GetWindowProcesses())
        if (windowProcess.MainWindowTitle.Contains(windowTitleContains))
            return windowProcess;

    return null;
}

However, some strange behavior is going on. GetProcessByWindowTitle() will return ONE but not BOTH of the processes. I'm assuming that because there are two windows that there must be two processes.

Which Process it returns is dependent on which was the last window that I clicked with my mouse.

For example, if I last clicked Window A; then GetProcessByWindowTitle("SideSync") will return a Process, but then GetProcessByWindowTitle("SAMSUNG") will return void.

...and vice versa if I last clicked Window B, GetProcessByWindowTitle("SideSync") will return a void, but then GetProcessByWindowTitle("SAMSUNG") will return Process.

Client code:

[Ignore("Requires starting SideSync and clicking one of the windows. Only the last clicked will return a Process.")]
[Test]
public void NonMinimizedWindowProcessIsDetected()
{

    Process p1 = Windows.GetProcessByWindowTitle("SAMSUNG");

    if(p1==null) { Console.WriteLine("SAMSUNG process is null.");}
    else { Console.WriteLine("SAMSUNG process detected.");}

    Process p2 = Windows.GetProcessByWindowTitle("SideSync");

    if (p2 == null) { Console.WriteLine("SideSync process is null."); }
    else { Console.WriteLine("SideSync process detected."); }
}

My goal is to show Window B. My issue is that this is only possible if I clicked on it last, which creates an unwanted dependency. I want to be able to show window B independent of any click order.

like image 481
sapbucket Avatar asked Feb 04 '23 21:02

sapbucket


1 Answers

I took some time and tried to recreate your problem. According to my analysis (which took some time because I did not get SideSync correctly running at first ;-)) there exists only one process running under the name SideSync.exe which hosts multiple windows.

I guess the "flaw" in your approach is that you are trying to fetch the process by the MainWindowTitle. But if you use the following code snippet you will see, that the MainWindowTitle changes depending on the currently active window in that process.

while (true)
{
    var processes = Process.GetProcesses();

    foreach (var process in processes)
    {
        if (process.ProcessName != "SideSync")
            continue;

        Console.WriteLine($"{process.ProcessName}, {process.MainWindowTitle}, {process.MainWindowHandle.ToString()}");
    }

    Thread.Sleep(1000);
}

In my case the MainWindowTitle changed between different titles like:

SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Notifier, 1903168
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, ToolTip, 3148196
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, ToolTip, 3148196
SideSync, ToolTip, 3148196
SideSync, Samsung Galaxy S7, 3082852
SideSync, Samsung Galaxy S7, 3082852
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728
SideSync, SideSync, 1313728

As you can see the output also includes titles like Notifier or ToolTip which appear when a Notification Window appears or you move the mouse over an Icon in the SideSync application. As can further be seen in the output the MainWindowHandle of the active window changes too of course.

So in my opinion you just need to fetch the SideSync process with Process.GetProcessesByName("SideSync"), nothing else.

I hope this helps ;-)

Update:

According to the OPs comment he needs a way to open one specific window of the SideSync process indipendent on which was opened the last time. In order to achieve this, the first step is to find the corresponding window handles of the windows that belong to the SideSync process.

I have based the following code on the code of the answer from cREcker, who has based his answer on the resource Getting a list of all the open windows.

The method GetOpenWindowsByProcessId of the following class allows to get the handle of all windows which belong to the specified process id.

public static class OpenWindowGetter
{
    public static IDictionary<string, IntPtr> GetOpenWindowsByProcessId(int processId)
    {
        IntPtr shellWindow = GetShellWindow();
        Dictionary<string, IntPtr> windows = new Dictionary<string, IntPtr>();

        EnumWindows(delegate (IntPtr hWnd, int lParam)
        {
            uint ownerProcessId;
            GetWindowThreadProcessId(hWnd, out ownerProcessId);

            if (ownerProcessId != processId)
                return true;

            if (hWnd == shellWindow)
                return true;

            if (!IsWindowVisible(hWnd))
                return true;

            int length = GetWindowTextLength(hWnd);

            if (length == 0)
                return true;

            StringBuilder builder = new StringBuilder(length);
            GetWindowText(hWnd, builder, length + 1);
            windows[builder.ToString()] = hWnd;

            return true;

        }, 0);

        return windows;
    }

    private delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);

    [DllImport("USER32.DLL")]
    private static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);

    [DllImport("USER32.DLL")]
    private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

    [DllImport("USER32.DLL")]
    private static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("USER32.DLL")]
    private static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("USER32.DLL")]
    private static extern IntPtr GetShellWindow();

    [DllImport("user32.dll", SetLastError = true)]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
}

In addition we will need a way to "show" a window.

private const int SW_SHOWNORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
private const int SW_SHOWMAXIMIZED = 3;

[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);

With this knowledge we can now write code like the following one (which can for sure be optimized in thousand different ways):

private static void ShowPhoneScreenWindow()
{
    const string PROCESSNAME = "sidesync";
    const string WINDOWTITLE = "samsung";

    var process = Process.GetProcessesByName(PROCESSNAME).FirstOrDefault();

    if (process == null)
        throw new InvalidOperationException($"No process with name {PROCESSNAME} running.");

    var windowHandles = OpenWindowGetter.GetOpenWindowsByProcessId(process.Id);
    IntPtr windowHandle = IntPtr.Zero;

    foreach (var key in windowHandles.Keys)
        if (key.ToLower().StartsWith(WINDOWTITLE))
        {
            windowHandle = windowHandles[key];
            break;
        }

    if (windowHandle == IntPtr.Zero)
        throw new InvalidOperationException($"No window with title {WINDOWTITLE} hosted.");

    ShowWindowAsync(windowHandle, SW_SHOWNORMAL);
}
like image 167
Markus Safar Avatar answered Feb 06 '23 12:02

Markus Safar