tags:

views:

1146

answers:

4

I'm interested in working on a plugin for Keepass, the open-source password manager. Right now, Keepass currently detects what password to copy/paste for you based off of the window title. This prevents Keepass from detecting the current password you need for apps that don't actively update their window title based on the current site (Chrome for instance).

How can I walk through another processes window elements (buttons, labels, textbox) similar to how Spy++ works? When you run Spy++ you can hover over other programs windows and get all kinds of information about various properties concerning various controls (labels, textboxes, etc). Ideally, I'd like my Keepass plugin to enhance the current window detection by walking through the active window's elements in an effort to find a matching account to copy/paste the password.

How can I walk other processes window elements and be able to retrieve label and textbox values using C#?

+2  A: 

You can use EnumWindows to find every top-level Chrome window and then call EnumChildWindows recursively to get every child of the main window. Alternatively, once you have the main Chrome window, you can use GetWindow to manually navigate the tree since you probably know what you're looking for (3rd child's children collection or something similar).

Once you find your window, you can use SendMessage with a WM_GETTEXT parameter to read the window's label.

Blindy
@Blindy, thank you for the info! I'm looking to do this to any active window, not just Chrome. Once I have the handles to the windows/child-windows, how do I navigate through that windows elements?
Simucal
These are all native Windows API calls though and not managed .NET Framework calls so they require P/Invoke from CLR.
DrJokepu
@DrJokepu, Aye. I figured I'd have to be doing some P/Invoke magic.
Simucal
@Blindy, Once I have a reference to a window, how can I iterate through all its buttons, labels, textbox, etc?
Simucal
Browsers usually use windowless controls, due to the 10k handle limit in windows. You may need to write specific hooks for each browser.
arul
Well if you can see it in Spy++, you can see it with this too. To navigate the windows, you use `GetWindow` with `GW_HWNDNEXT`/`GW_HWNDPREV` for siblings and `GW_CHILD` for children.
Blindy
A: 

For the functionality of pointing to a window. You need to SetCapture() so that you get mouse messages that are outside of your window. Then use WindowFromPoint() to convert a mouse position to a Window. You will need to convert the moust position from client coordinates to window coordinates first.

If you try an call SetCapture() anywhere but on a mouse click message, you will probably be ignored. This is the reason that Spy++ makes you click on an Icon and drag and drop it on the window you want to point to.

John Knoeller
What I'm attempting to do is simply iterate through a windows elements without the mouse interaction. Once I call EnumChildWindows, how can I get the text of whatever elements belong to that child window?
Simucal
Send the window messages. WM_GETTEXT, etc. If that's not good enought, you need to define what you mean by 'elements'?
John Knoeller
@John Knoeller, all text labels, textboxes and buttons.
Simucal
`EnumChildWindows` of your child windows. repeat recursively until you run out of generations.
John Knoeller
@Simucal, where you able to enumerate all elements of the child window and get the corresponding values? if yes, please share
Zee99
+2  A: 

Have a look at this article here which contains information about the Managed Spy and why the author wrote the tool.

Hope this helps, Best regards, Tom.

tommieb75
Keep in mind that the application you're referring to only works for *managed* apps, not unmanaged ones like most browsers are.
Blindy
+3  A: 

I've being answering to smth like this here: How can I detect if a thread has windows handles?. Like it as already said the main idea is to enumerate through process windows, and their child windows using EnumWindows and EnumChildWindows API calls to get window handles and then call GetWindowText or SendDlgItemMessage with WM_GETTEXT to get window text. I've modified an some code to make an example which should be doing what you need (sorry it's a bit long :). It iterates through processes and their windows and dumps window text into console.

static void Main(string[] args)
{
    foreach (Process procesInfo in Process.GetProcesses())
    {
        Console.WriteLine("process {0} {1:x}", procesInfo.ProcessName, procesInfo.Id);
        foreach (ProcessThread threadInfo in procesInfo.Threads)
        {
            // uncomment to dump thread handles
            //Console.WriteLine("\tthread {0:x}", threadInfo.Id);
            IntPtr[] windows = GetWindowHandlesForThread(threadInfo.Id);
            if (windows != null && windows.Length > 0)
                foreach (IntPtr hWnd in windows)
                    Console.WriteLine("\twindow {0:x} text:{1} caption:{2}",
                        hWnd.ToInt32(), GetText(hWnd), GetEditText(hWnd));
        }
    }
    Console.ReadLine();
}

private static IntPtr[] GetWindowHandlesForThread(int threadHandle)
{
    _results.Clear();
    EnumWindows(WindowEnum, threadHandle);
    return _results.ToArray();
}

// enum windows

private delegate int EnumWindowsProc(IntPtr hwnd, int lParam);

[DllImport("user32.Dll")]
private static extern int EnumWindows(EnumWindowsProc x, int y);
[DllImport("user32")]
private static extern bool EnumChildWindows(IntPtr window, EnumWindowsProc callback, int lParam);
[DllImport("user32.dll")]
public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);

private static List<IntPtr> _results = new List<IntPtr>();

private static int WindowEnum(IntPtr hWnd, int lParam)
{
    int processID = 0;
    int threadID = GetWindowThreadProcessId(hWnd, out processID);
    if (threadID == lParam)
    {
        _results.Add(hWnd);
        EnumChildWindows(hWnd, WindowEnum, threadID);
    }
    return 1;
}

// get window text

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetWindowTextLength(IntPtr hWnd);

private static string GetText(IntPtr hWnd)
{
    int length = GetWindowTextLength(hWnd);
    StringBuilder sb = new StringBuilder(length + 1);
    GetWindowText(hWnd, sb, sb.Capacity);
    return sb.ToString();
}

// get richedit text 

public const int GWL_ID = -12;
public const int WM_GETTEXT = 0x000D;

[DllImport("User32.dll")]
public static extern int GetWindowLong(IntPtr hWnd, int index);
[DllImport("User32.dll")]
public static extern IntPtr SendDlgItemMessage(IntPtr hWnd, int IDDlgItem, int uMsg, int nMaxCount, StringBuilder lpString);
[DllImport("User32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);

private static StringBuilder GetEditText(IntPtr hWnd)
{
    Int32 dwID = GetWindowLong(hWnd, GWL_ID);
    IntPtr hWndParent = GetParent(hWnd);
    StringBuilder title = new StringBuilder(128);
    SendDlgItemMessage(hWndParent, dwID, WM_GETTEXT, 128, title);
    return title;
}

hope this helps, regards

serge_gubenko