Search code examples
vb.netwinformswinapiwindows-screensaver

Deactivate a ScreenSaver programmatically


I have an ACEPC T6, with a 7 inch LCD screen connected via HDMI, and a USB touchscreen.
This system is connected to a processor board via a USB serial link which reads various sensors and feeds the data back to the PC.
The program on the PC is written with VB.NET 2019. Everything works fine with one exception.

I use the ScreenSaver to blank the screen after 10 minutes of activity, and touching the screen brings things back fine.

My problem is that I have a PIR sensor connected to the processor, which sends a command back to the PC. My objective is for this to simulate the screen being touched, and the display be restored, i.e. when someone walks into the room, the display returns.

I have tried all sorts of options, simulating mouse effects, sending key strokes, but all to no avail.
Any help would be appreciated.

I should have mentioned I am running Windows 10 Pro.


Solution

  • You're probably not able to have a positive result using SendInput() or other functions because a ScreenSaver runs in special Desktop, named (guess what) Screen-saver.
    You need to get the handle to this Desktop to actively interact with it or its Windows.

    You can use the SystemParametersInfo function, passing SPI_GETSCREENSAVERRUNNING to determine whether a ScreenSaver is currently active, then call OpenDesktop() get the handle of the Desktop (using its name), find the ScreenSaver Window (the ScreenSaver is a standard executable) and post a WM_CLOSE message to close it.

    Here, I'm using EnumDesktopWindows to find the ScreenSaver.
    (The ScreenSaver Window should the be Foreground Window anyway).
    In case it fails, we can try to close the Desktop, calling CloseDesktop().

    Some more possibly interesting info related to ScreenSaver here:
    Handling Screen Savers

    If you have a procedure that receives a Command through a serial port or something similar, verify whether this procedure is run in a Thread other than the UI Thread, in case you need to access the UI in this occasion.

    Add this code to the procedure: it checks whether a ScreenSaver is active and tries to close it.
    It's been tested in both Windows 10 (19H2) and Windows 7 SP1.
    .Net Framework 4.8 - VB.Net v.15

    NOTE: your app is meant to run as a 64bit process. If you have set Prefer 32-bit in your Project's Build option, you need to deselect it.

    ► Assuming this procedure is run in the UI Thread. Me.Activate() is not strictly required and can be used from the UI Tread only. Remove it if your code runs on a different Thread.

    Protected Sub SomeProcedure()
        If NativeMethods.GetScreenSaverActiveState() Then
            NativeMethods.TerminateScreenSaver()
            Me.Activate()
        End If
    End Sub
    

    NativeMethods class and helper methods:

    Imports System.Runtime.InteropServices
    Imports System.Security.Permissions
    
    Friend Class NativeMethods
        Public Shared Sub TerminateScreenSaver()
            Try
                TerminateScreenSaverCore()
            Catch wex As Win32Exception
                MessageBox.Show(wex.Message)
            End Try
        End Sub
    
        Public Shared Function GetScreenSaverActiveState() As Boolean
            Dim scState As IntPtr = IntPtr.Zero
            SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0UI, scState, 0UI)
            Return scState <> IntPtr.Zero
        End Function
    
        <UIPermission(SecurityAction.Demand, Action:=SecurityAction.Demand, Window:=UIPermissionWindow.AllWindows)>
        Private Shared Sub TerminateScreenSaverCore()
            Call New SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand()
    
            Dim hDesktop As IntPtr = OpenDesktop("Screen-saver", 0, False,
                DesktopAccessRights.DESKTOP_READOBJECTS Or DesktopAccessRights.DESKTOP_WRITEOBJECTS)
    
            If hDesktop <> IntPtr.Zero Then
                If Not EnumDesktopWindows(hDesktop,
                Function(hWnd, lp)
                    If IsWindowVisible(hWnd) Then CloseWindow(hWnd)
                    Return True
                End Function, IntPtr.Zero) Then
                    If Not CloseDesktop(hDesktop) Then
                        CloseWindow(GetForegroundWindow())
                    End If
                End If
            End If
        End Sub
    
        Private Shared Sub CloseWindow(hWnd As IntPtr)
            PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero)
        End Sub
    
        Private Const WM_CLOSE As Integer = &H10
        Private Const SPI_GETSCREENSAVERRUNNING As UInteger = &H72
    
        Public Enum DesktopAccessRights As Long
            DESKTOP_CREATEMENU = &H4L
            DESKTOP_CREATEWINDOW = &H2L
            DESKTOP_ENUMERATE = &H40L
            DESKTOP_HOOKCONTROL = &H8L
            DESKTOP_JOURNALPLAYBACK = &H20L
            DESKTOP_JOURNALRECORD = &H10L
            DESKTOP_READOBJECTS = &H1L
            DESKTOP_SWITCHDESKTOP = &H100L
            DESKTOP_WRITEOBJECTS = &H80L
        End Enum
    
        Private Delegate Function EnumWindowsProc(hDesktop As IntPtr, lParam As IntPtr) As Boolean
    
        <DllImport("user32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Private Shared Function OpenDesktop(
            lpszDesktop As String,
            dwFlags As UInteger,
            <[In], MarshalAs(UnmanagedType.Bool)> fInherit As Boolean,
            dwDesiredAccess As DesktopAccessRights) As IntPtr
        End Function
    
        <DllImport("user32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Private Shared Function CloseDesktop(hDesktop As IntPtr) As Boolean
        End Function
    
        <DllImport("user32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Private Shared Function EnumDesktopWindows(
            hDesktop As IntPtr,
            callback As EnumWindowsProc,
            lParam As IntPtr) As Boolean
        End Function
    
        <DllImport("user32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Public Shared Function GetForegroundWindow() As IntPtr
        End Function
    
        <DllImport("user32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Private Shared Function IsWindowVisible(hWnd As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function
    
        <DllImport("user32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
        Private Shared Function PostMessage(hWnd As IntPtr, msg As UInteger, wParam As IntPtr, lParam As IntPtr) As Boolean
        End Function
    
        <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
        Private Shared Function SystemParametersInfo(uiAction As UInteger, uiParam As UInteger, ByRef pvParam As IntPtr, fWinIni As UInteger) As Boolean
        End Function
    End Class
    

    C# version, just in case:

    using System.Runtime.InteropServices;
    using System.Security.Permissions;
    
    internal static class NativeMethods
    {
        public static void TerminateScreenSaver()
        {
            try {
                TerminateScreenSaverCore();
            }
            catch(Win32Exception wex){
                MessageBox.Show(wex.Message);
            }
        }
    
        public static bool GetScreenSaverActiveState()
        {
            var scState = IntPtr.Zero;
            SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0U, ref scState, 0U);
            return scState != IntPtr.Zero;
        }
    
        [UIPermission(SecurityAction.Demand, Action = SecurityAction.Demand, Window = UIPermissionWindow.AllWindows)]
        private static void TerminateScreenSaverCore()
        {
            new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
    
            IntPtr hDesktop = OpenDesktop("Screen-saver", 0, false, 
                DesktopAccessRights.DESKTOP_READOBJECTS | DesktopAccessRights.DESKTOP_WRITEOBJECTS);
            if (hDesktop != IntPtr.Zero) {
                if (!EnumDesktopWindows(hDesktop, (hWnd, lp)=> {
                    if (IsWindowVisible(hWnd)) CloseWindow(hWnd);
                    return true;
                }, IntPtr.Zero) || !CloseDesktop(hDesktop)) {
                    CloseWindow(GetForegroundWindow());
                }
            }
        }
    
        private static void CloseWindow(IntPtr hWnd)
        {
            PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        }
    
        private const uint SPI_GETSCREENSAVERRUNNING = 0x0072;
        private const int WM_CLOSE = 0x0010;
    
        public enum DesktopAccessRights : long
        {
            DESKTOP_CREATEMENU = 0x0004L,        // Required to create a menu on the desktop.
            DESKTOP_CREATEWINDOW = 0x0002L,      // Required to create a window on the desktop.
            DESKTOP_ENUMERATE = 0x0040L,         // Required for the desktop to be enumerated.
            DESKTOP_HOOKCONTROL = 0x0008L,       // Required to establish any of the window hooks.
            DESKTOP_JOURNALPLAYBACK = 0x0020L,   // Required to perform journal playback on a desktop.
            DESKTOP_JOURNALRECORD = 0x0010L,     // Required to perform journal recording on a desktop.
            DESKTOP_READOBJECTS = 0x0001L,       // Required to read objects on the desktop.
            DESKTOP_SWITCHDESKTOP = 0x0100L,     // Required to activate the desktop using the SwitchDesktop function.
            DESKTOP_WRITEOBJECTS = 0x0080L       // Required to write objects on the desktop.
        }
    
        private delegate bool EnumWindowsProc(IntPtr hDesktop, IntPtr lParam);
    
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern IntPtr OpenDesktop(
            string lpszDesktop, 
            uint dwFlags, 
            [In, MarshalAs(UnmanagedType.Bool)]bool fInherit, 
            DesktopAccessRights dwDesiredAccess);
    
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool CloseDesktop(IntPtr hDesktop);
    
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsProc callback, IntPtr lParam);
    
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool IsWindowVisible(IntPtr hWnd);
    
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern IntPtr GetForegroundWindow();
    
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
    
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref IntPtr pvParam, uint fWinIni);
    }