views:

587

answers:

4

How can I programmatically (in C#) determine, if ANOTHER foreign application (native, java, .NET or whatever...) is currently demanding user input? Can this be done fully in Managed code?

What I'm looking for is the implementation of:

static Boolean IsWaitingForUserInput(String processName)
{
    ???
}

By demanding user input I mean when an application asks the user to enter some data or quit an error message (Modal dialogs) and is not able to perform its normal tasks anymore. A drawing application that is waiting for the user to draw something is not meant here.

PS: After edits to reflect the comments at the bottom and make the concern clearer, some comments and answers may not be 100% consistent with the question. Take this into account when evaluating the answers and remarks.

A: 

If I understand you well, you may try to enumerate the process's threads and check their states. Windows Task Manager does something similar. This however will require Win32 functions - Thread32First and Thread32Next among others - but you can achieve this by the simplest use of P/Invoke in C#:

    [DllImport("Executor.dll")]
    public static extern bool Thread32First(IntPtr handle, IntPtr threadEntry32);

(Precise signature may differ).

EDIT: Ok, there are corresponding functions in the .NET library.

Michal Czardybon
Enumerating the threads and check their state was not enough, like MSalters remarked, Input-Idle is the state where the application processes messages, not really 100% signalizing a demand for input from the user.
jdehaan
+8  A: 

It's in general impossible. Take for instance a common kind of application, a word processor. Nowadays that will be running spellchecks in the background, it periodically auto-saves your document, etcetera. Yet from a users perspective it's waiting for input all the time.

Another common case would be a slideshow viewer. At any moment in time you could press a key to advance a slide. Yet your typical user would not view this as "waiting for input".

To summarize: "waiting for input" is a subjective state and therefore cannot be determined programmatically.

MSalters
impossible to solve in a general sense. The pattern to apply would be a strategy; you'd have to determine how to examine the each application you need to solve this problem for, and figure out what constitutes a "waiting for user input" state. As one of the other answers to this question shows, it can be solved for Excel using a modal dialog to "demand" user input.
Oplopanax
Impossible is nothing ;-) I agree it's a matter of definition. I rewrote the question to be more precise, there are definitively approaches to solve the problem like the one I posted (best I could get and I am happy with it)
jdehaan
there's no impossible in most of these questions. There is only "how bad do you want this"
Oplopanax
Thanks Oplopanax, I sometimes wonder that an answer that definitively does not help to solve a real problem I and probably others have (poorly or not but at least better than nothing) gets upvoted so many times!!! [By the time of this response the question was written in a misleading way this should not be a comment to blame MSalters ;-)]
jdehaan
+2  A: 

How do you like this?

I worked out a solution that seems to work, please notify me in case of problems with this code so I also gain benefit of improvements. It works for Excel as far as I tested. The only issue I dislike is that I had to use unmanaged calls. It also handles the case when an application is based on a dialog like for MFC, derived from CDialog.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;

namespace Util
{
    public class ModalChecker
    {
        public static Boolean IsWaitingForUserInput(String processName)
        {
            Process[] processes = Process.GetProcessesByName(processName);
            if (processes.Length == 0)
                throw new Exception("No process found matching the search criteria");
            if (processes.Length > 1)
                throw new Exception("More than one process found matching the search criteria");
            // for thread safety
            ModalChecker checker = new ModalChecker(processes[0]);
            return checker.WaitingForUserInput;
        }

        #region Native Windows Stuff
        private const int WS_EX_DLGMODALFRAME = 0x00000001;
        private const int GWL_EXSTYLE = (-20);
        private delegate int EnumWindowsProc(IntPtr hWnd, int lParam);
        [DllImport("user32")]
        private extern static int EnumWindows(EnumWindowsProc lpEnumFunc, int lParam);
        [DllImport("user32", CharSet = CharSet.Auto)]
        private extern static uint GetWindowLong(IntPtr hWnd, int nIndex);
        [DllImport("user32")]
        private extern static uint GetWindowThreadProcessId(IntPtr hWnd, out IntPtr lpdwProcessId);
        #endregion

        // The process we want the info from
        private Process _process;
        private Boolean _waiting;

        private ModalChecker(Process process)
        {
            _process = process;
            _waiting = false; //default
        }

        private Boolean WaitingForUserInput
        {
            get
            {
                EnumWindows(new EnumWindowsProc(this.WindowEnum), 0);
                return _waiting;
            }
        }

        private int WindowEnum(IntPtr hWnd, int lParam)
        {
            if (hWnd == _process.MainWindowHandle)
                return 1;
            IntPtr processId;
            GetWindowThreadProcessId(hWnd, out processId);
            if (processId.ToInt32() != _process.Id)
                return 1;
            uint style = GetWindowLong(hWnd, GWL_EXSTYLE);
            if ((style & WS_EX_DLGMODALFRAME) != 0)
            {
                _waiting = true;
                return 0; // stop searching further
            }
            return 1;
        }
    }
}
jdehaan
this is probably the only definition of "waiting for user input" that is valid. a modal dialog. we should probably call it "demanding user input"
slf
Good idea! I know this was more or less matter of definition. This piece of code is hopefully a good base for other people wanting to do the same thing.
jdehaan
One downside of it is that it misses detection of a triggered jit debugger... Any ideas to solved it (not only for one special jit debugger)?
jdehaan
A: 

If possible, rewrite the other code to be a concurrent input processor (similar to the algorithm for a concurrent web server):

Wait for input
Fork process
  Parent: Repeat
  Child: (Worker) handle input

Of course, you could still have your function:

static Boolean IsWaitingForUserInput(String processName) {
    return true;
}
Steven