9

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.

sapbucket
  • 6,795
  • 15
  • 57
  • 94
  • You said you believe the window you are looking for is a child of another. Have you verified this with a tool like Microsoft's Inspect? – tj-cappelletti Oct 29 '18 at 19:26
  • The screenshot clearly shows that there are 2 top-level windows opened under that process name. Neither is a child of the other, they're siblings actually. – Alejandro Oct 29 '18 at 19:54
  • @Alejandro - it is possible that you are correct. However, when minimized only "SideSync" process name is returned by GetWindowProcesses(). On the other hand, if I maximize the two windows, BOTH "SideSync" and "Samsung..." processes are returned by GetWindowProcesses(). It is a mystery and the reason I am posting this question to find out why "Samsung..." isn't also listed... – sapbucket Oct 29 '18 at 19:58
  • @sapbucket Child windows cannot be maximized or minimized, they live entirely within their parent, maybe those two are a normal window and a dialog box, but definitely they're not parent-child. Most likely however, both are owned by the same process. As for GetWindowProcesses, difficult to say without seeing its definition. – Alejandro Oct 29 '18 at 20:18
  • @Alejandro - I added a code snippet for GetWindowProcesses to the bottom of the server code section. As you can see it's simply using System.Diagnostic.Process – sapbucket Oct 29 '18 at 21:10
  • @Alejandro - I have confirmed that you are correct. Neither window has a child-parent relationship. Like you said before they both appear to be normal windows. – sapbucket Oct 29 '18 at 21:11
  • I am going to re-write my question based upon the comments I received. I'll be done in 20 minutes. – sapbucket Oct 29 '18 at 21:28
  • Okay my apologies but this newly stated question is much more accurate. – sapbucket Oct 29 '18 at 22:01
  • @administrators: how may I increase the bounty? I would like to encourage other answers by sweetening the pot. The UI seems to think that I can only +100 and I have to award it. I don't see any links or buttons to modify it. – sapbucket Nov 08 '18 at 21:20

1 Answers1

12

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);
}
Markus Safar
  • 6,324
  • 5
  • 28
  • 44
  • 1
    My apologies for the late reply. The bounty is about to expire: if this solution works I will award +100 even after it expires. I'm going to try your suggestion now. – sapbucket Nov 08 '18 at 20:41
  • 1
    This approach did not work for me. If I use Process.GetProcessesByName("SideSync"), and then BringToTop() the process returned, it brings up Window A, only. – sapbucket Nov 08 '18 at 20:57
  • 1
    If I minimize Window A, and manually show Window B, and then call Process.GetProcessesByName("SideSync"), it does return Window B. But that is my exact point: I want to be able to bring up Window B when both Window A and B are minimized. There should be no depedency on "what I clicked on last". – sapbucket Nov 08 '18 at 21:01
  • @sapbucket, I see, I have updated my answer in the hope that I understood your problem accordingly and that it helps you ;-) – Markus Safar Nov 09 '18 at 16:15
  • @sapbucket, glad I could help. Please consider upvoting the answer of [cREcker](https://stackoverflow.com/questions/7268302/get-the-titles-of-all-open-windows/43640787#43640787) as well as my answer is based on his ;-) – Markus Safar Nov 12 '18 at 10:25