Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect a Winforms app has been idle for certain amount of time

Tags:

c#

.net

winforms

What is the best way, to detect if a C# Winforms application has been idle for a certain period of times?

If a user decides to ALT+TAB and do some work with Microsoft Word or whatever for 30 minutes, leaving our app unused, I want our app to self-terminate.

This is the accepted answer for a similar question: Check if an application is idle for a time period and lock it

However, the answer is pertinent to Windows being idle for a period of time, not a specific application. I want our application to terminate if it's not been used for say, 30 minutes.

I looked at this:

http://www.codeproject.com/Articles/13756/Detecting-Application-Idleness

However I read in the comments that this doesn't work for multi-threaded applications, of which our app is one. Our app has a main form, which spawns modal and modeless forms, which use Async Await to fill grids etc.

Then I looked at SetWindowsHookEx, unsure if that would work.

Surely someone has a solution (compatible with .NET 4.5 hopefully ) :)

TIA

like image 511
Scott Avatar asked Apr 05 '16 15:04

Scott


2 Answers

There are many ways to do it and the answer somewhat depends on what you need to do. You are clear and specific about what you need. The following is something I developed that probably would fit your requirements. What it is doing is using Application.Idle to determine when the application has finished processing messages then it sets a timer and filters (listens to) all messages for the application and if a relevant message (such as mouse or keyboard) is received then it resets the timer. It ignores mouse move since it is possible to move the mouse over the application without using the application. It has been a while since I wrote that so I am not sure of the details but I could figure it out if necessary. Note that this is a console program to make the sample easier to try but the code is intended for a forms application.

using System;
using System.Security.Permissions;
using System.Windows.Forms;

namespace _121414
{
    static class Program
    {
        public static Timer IdleTimer = new Timer();
        const int MinuteMicroseconds = 60000;
        static Form1 f = null;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            LeaveIdleMessageFilter limf = new LeaveIdleMessageFilter();
            Application.AddMessageFilter(limf);
            Application.Idle += new EventHandler(Application_Idle);
            IdleTimer.Interval = MinuteMicroseconds;    // One minute; change as needed
            IdleTimer.Tick += TimeDone;
            IdleTimer.Start();
            f = new Form1();
            Application.Run(f);
            Application.Idle -= new EventHandler(Application_Idle);
        }

        static private void Application_Idle(Object sender, EventArgs e)
        {
            if (!IdleTimer.Enabled)     // not yet idling?
                IdleTimer.Start();
        }

        static private void TimeDone(object sender, EventArgs e)
        {
            IdleTimer.Stop();   // not really necessary
            MessageBox.Show("Auto logoff");
            f.Close();
        }

    }

    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public class LeaveIdleMessageFilter : IMessageFilter
    {
        const int WM_NCLBUTTONDOWN = 0x00A1;
        const int WM_NCLBUTTONUP = 0x00A2;
        const int WM_NCRBUTTONDOWN = 0x00A4;
        const int WM_NCRBUTTONUP = 0x00A5;
        const int WM_NCMBUTTONDOWN = 0x00A7;
        const int WM_NCMBUTTONUP = 0x00A8;
        const int WM_NCXBUTTONDOWN = 0x00AB;
        const int WM_NCXBUTTONUP = 0x00AC;
        const int WM_KEYDOWN = 0x0100;
        const int WM_KEYUP = 0x0101;
        const int WM_MOUSEMOVE = 0x0200;
        const int WM_LBUTTONDOWN = 0x0201;
        const int WM_LBUTTONUP = 0x0202;
        const int WM_RBUTTONDOWN = 0x0204;
        const int WM_RBUTTONUP = 0x0205;
        const int WM_MBUTTONDOWN = 0x0207;
        const int WM_MBUTTONUP = 0x0208;
        const int WM_XBUTTONDOWN = 0x020B;
        const int WM_XBUTTONUP = 0x020C;

        // The Messages array must be sorted due to use of Array.BinarySearch
        static int[] Messages = new int[] {WM_NCLBUTTONDOWN,
            WM_NCLBUTTONUP, WM_NCRBUTTONDOWN, WM_NCRBUTTONUP, WM_NCMBUTTONDOWN,
            WM_NCMBUTTONUP, WM_NCXBUTTONDOWN, WM_NCXBUTTONUP, WM_KEYDOWN, WM_KEYUP,
            WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN, WM_RBUTTONUP,
            WM_MBUTTONDOWN, WM_MBUTTONUP, WM_XBUTTONDOWN, WM_XBUTTONUP};

        public bool PreFilterMessage(ref Message m)
        {
            if (m.Msg == WM_MOUSEMOVE)  // mouse move is high volume
                return false;
            if (!Program.IdleTimer.Enabled)     // idling?
                return false;           // No
            if (Array.BinarySearch(Messages, m.Msg) >= 0)
                Program.IdleTimer.Stop();
            return false;
        }
    }
}
like image 132
user34660 Avatar answered Oct 31 '22 05:10

user34660


You would need to decide what "idle" means. I assume no mouse or key input received by the application during the required time.

You could add a message filter that will see all application-wide Windows messages. See Application.AddMessageFilter. Check for the message codes for key down and mouse down, and record the last time that they occur, but return false so that all messages are handled as normal by the runtime. Likely codes are:

WM_LBUTTONDOWN = 513
RBUTTONDOWN = 516
WM_KEYDOWN = 256

Then in a separate timer elsewhere check when the last event is more than 30 minutes ago.

There are also potential issues with "terminating" the application. If the application has modal forms open, it sounds as if it might not be in a particularly safe state to kill abruptly.

like image 27
Stuart Whitehouse Avatar answered Oct 31 '22 05:10

Stuart Whitehouse