9

I'm writing an app that at times will send notifications to the user in the form of toaster messages.

If the user is not there, he can't see the notification. So what I wanna do is be able to check if the user has locked the screen or if the screensaver happens to be activated.

Any notification that is triggered while the user cannot see it will be delayed and shown when the user logs back in and resumes his session.

I'm on Windows 7 myself, but I'd prefer a solution that works universally for Windows XP and up.

Pieter
  • 31,619
  • 76
  • 167
  • 242
  • Aha, they disabled hotlinking. Does this work? http://images.google.com/imgres?imgurl=http://blog.fatbusinessman.com/blog-post-images/msn-pics-in-popups.png&imgrefurl=http://blog.fatbusinessman.com/archives/2005/04/07/msn-messenger-7-review/&usg=__Ed4o5d78BK01ZJ3K6penXkWgsJc=&h=287&w=341&sz=21&hl=en&start=0&zoom=1&tbnid=_fUzeg729w42dM:&tbnh=157&tbnw=187&prev=/images%3Fq%3Dmsn%2Bmessenger%2Btoaster%26hl%3Den%26biw%3D1600%26bih%3D775%26tbs%3Disch:1&itbs=1&iact=rc&dur=529&ei=jQS7TJnID9ug4QasnODRDQ&oei=jQS7TJnID9ug4QasnODRDQ&esq=1&page=1&ndsp=33&ved=1t:429,r:11,s:0&tx=146&ty=89 – Pieter Oct 17 '10 at 14:13

6 Answers6

28

There is no documented way to find out if the workstation is currently locked. You can however get a notification when it un/locks. Subscribe the SystemEvents.SessionSwitch event, you'll get SessionSwitchReason.SessionLock and Unlock.

The sceen saver is troublesome too. Your main window gets the WM_SYSCOMMAND message, SC_SCREENSAVE when the screen saver turns on. You can pinvoke SystemParametersInfo to check if it running. You'll find sample code for this in my answer in this thread.

There is no good way to find out if the user fell asleep.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
11

I have recently checked this code again from a previous blog post to ensure it works on versions of Windows XP to 7, x86 and x64 and cleaned it up a bit.

Here is the latest minimalist code that checks if the workstation is locked and if the screensaver is running wrapped in two easy to use static methods:

using System;
using System.Runtime.InteropServices;

namespace BrutalDev.Helpers
{
  public static class NativeMethods
  {
    // Used to check if the screen saver is running
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool SystemParametersInfo(uint uAction, 
                                                   uint uParam, 
                                                   ref bool lpvParam,
                                                   int fWinIni);

    // Used to check if the workstation is locked
    [DllImport("user32", SetLastError = true)]
    private static extern IntPtr OpenDesktop(string lpszDesktop,
                                             uint dwFlags,
                                             bool fInherit,
                                             uint dwDesiredAccess);

    [DllImport("user32", SetLastError = true)]
    private static extern IntPtr OpenInputDesktop(uint dwFlags,
                                                  bool fInherit,
                                                  uint dwDesiredAccess);

    [DllImport("user32", SetLastError = true)]
    private static extern IntPtr CloseDesktop(IntPtr hDesktop);

    [DllImport("user32", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SwitchDesktop(IntPtr hDesktop);

    // Check if the workstation has been locked.
    public static bool IsWorkstationLocked()
    {
      const int DESKTOP_SWITCHDESKTOP = 256;
      IntPtr hwnd = OpenInputDesktop(0, false, DESKTOP_SWITCHDESKTOP);

      if (hwnd == IntPtr.Zero)
      {
        // Could not get the input desktop, might be locked already?
        hwnd = OpenDesktop("Default", 0, false, DESKTOP_SWITCHDESKTOP);
      }

      // Can we switch the desktop?
      if (hwnd != IntPtr.Zero)
      {
        if (SwitchDesktop(hwnd))
        {
          // Workstation is NOT LOCKED.
          CloseDesktop(hwnd);
        }
        else
        {
          CloseDesktop(hwnd);
          // Workstation is LOCKED.
          return true;
        }
      }

      return false;
    }

    // Check if the screensaver is busy running.
    public static bool IsScreensaverRunning()
    {
      const int SPI_GETSCREENSAVERRUNNING = 114;
      bool isRunning = false;

      if (!SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref isRunning, 0))
      {
        // Could not detect screen saver status...
        return false;
      }

      if (isRunning)
      {
        // Screen saver is ON.
        return true;
      }

      // Screen saver is OFF.
      return false;
    }
  }
}

UPDATE: Code updated based on suggestions in the comments.

When the workstation is locked then the OpenInputDesktop method does not return a handle so we can fall-back on OpenDesktop for a handle to make sure it's locked by trying to switch. If it's not locked then your default desktop will not be activated since OpenInputDesktop will return a valid handle for the desktop you are viewing.

BrutalDev
  • 6,181
  • 6
  • 58
  • 72
  • 2
    Unless I'm missing something, this code would drive me crazy if running on a timer in an app I use. Pretty sure that will always force the Default desktop to be active, when not locked. I used multiple desktops, and do actual work in all of them. I don't use it, but MS has a desktops.exe app that lets you create multiple desktops and switch between them as well, not sure if a lot of people use it, if so, this code is not good for production. I think there is a way to get the current input desktop, that might be a better check. – eselk Jun 19 '12 at 22:09
  • 1
    Check MSDN for OpenInputDesktop & GetUserObjectInformation, to get active desktop name instead. – eselk Jun 19 '12 at 22:15
  • @eselk: Thanks for the valid comments. If you have different named desktops (very rare) then you are right, this will switch you to the default one on a timer loop. I will use your suggestion to use OpenInputDesktop instead, so the switch is silent. GetUserObjectInformation would not be useful since I don't need the desktop name if using OpenInputDesktop, just the handle is fine to do the switch. Will make the basic edits to the code when I get a chance to test it :) – BrutalDev Sep 04 '12 at 21:01
  • What about a TS environment? Any leads? – John Leidegren Apr 25 '14 at 07:03
  • @JohnLeidegren I'm not sure what you mean? An application running inside a TS session using the code above still works as expected, but only if the workstation you are connected to is locked. If you are implying that an application in your TS session should detect your host operating system's screensaver or lock status, well, that's impossible :) – BrutalDev Apr 26 '14 at 09:54
  • Well, in a TS environment there's more than one active user at any given moment. So these functions, do they really understand in which context they run? At the very least, this would require that you run a process in each session to get back the status of that session. It's definitely not impossible but it requires more work... – John Leidegren Apr 27 '14 at 11:32
  • @JohnLeidegren Each user in a TS session will have their own active desktop instance. The code will not detect if another user logged into the same machine has their session locked or the screensaver is active for them. Whoever runs the process that uses this code is the one who will be affected. If your process is running as the "system" such as Windows services, then there will be no active desktop to speak of and all logged in users will not be affected by this code. So yes, these functions run in the context of the user who started the process, no work is required. – BrutalDev Apr 27 '14 at 12:38
  • 1
    I've tried this code on Windows 10 and IsWorkstationLocked() always returns false. – Christopher Painter May 08 '20 at 13:47
  • This works on Windows 11. Thank you! – Microtribute Oct 13 '21 at 14:13
3

Use SystemParametersInfo to detect whether screen saver is running - the calltype is SPI_GETSCREENSAVERRUNNING. This is supported on Win2000 and above.

There is code from @dan_g on StackOverflow here to check if wksta is locked.

Community
  • 1
  • 1
Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
2
SystemEvents.SessionSwitch += new SessionSwitchEventHandler((sender, e) =>
        {
            switch (e.Reason)
            {
                //If Reason is Lock, Turn off the monitor.
                case SessionSwitchReason.SessionLock:
                    //SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, MONITOR_OFF);
                    MessageBox.Show("locked");
                    break;

                case SessionSwitchReason.SessionUnlock:
                    MessageBox.Show("unlocked");
                    break;
            }
        });

This will indicate when the session is Locked and Unlocked.

Chikku Jacob
  • 2,114
  • 1
  • 18
  • 33
  • Source of code above in case anyone wants more info on this: Anuraj Parameswaran, 2012, https://www.codeproject.com/articles/472795/how-to-turn-off-your-monitor-when-you-lock-your-ma – SunsetQuest Dec 31 '20 at 22:23
1

There are many reason why the user can't see your notifications also for example full screen video playback or that the user is just not there.

I suggest that instead of checking if the notification can be displayed check if the user is there, you can do that by monitoring the keyboard and mouse.

Shay Erlichmen
  • 31,691
  • 7
  • 68
  • 87
1

In researching this question I found a couple techniques that support detecting if the workstation is locked. One is really simple:

bool locked = Process.GetProcessesByName("logonui").Any();

This works because the logonui process is only running when the desktop is locked.

Another more complicated approach is to walk the system event log backwards looking for event ids 4800 and 4801. These indicate when a machine is locked and unclocked.

More detail can be found at:

https://superuser.com/questions/1170918/determine-remote-windows-screen-locked-or-unlocked-remotely

http://mctexpert.blogspot.com/2012/10/how-to-determine-if-client-on-your.html

The latter requires audit policies to be set which are the default anyways. I work in an enterprise IT organization so it's not a concern for me as I'm certain those settings are applied.

Christopher Painter
  • 54,556
  • 6
  • 63
  • 100
  • Thanks for this simple solution! – ecif Jul 30 '20 at 06:47
  • I actually found this to not be 100% reliable. I ended up creating a schedule task that runs on lock and unlock to update a registry value. That's more reliable but still not perfect. – Christopher Painter Jul 30 '20 at 18:53
  • Yeah, this actually doesn't work when I put it inside windows service... – ecif Aug 12 '20 at 12:07
  • 1
    Under remote desktop the loginui process is present when the local screen is locked, even though a remote user is logged in. – tomsv Sep 14 '20 at 11:11