1

Given a Windows 8 (.1) computer with the following power options: Power Options

And screensaver configured as: Screen Saver Settings

The goal is to programmatically turn the display on and "kill" the screensaver (so that it will be re-activated after idle time). (Note, that according to the settings it's possible that only the screensaver is on, or, that the display is off completely after the screensaver was on for about one minute).

What i have tried is:

SendMessage(HWND_Broadcast, WM_SysCommand, SC_MONITORPOWER, (LPARAM) - 1);

in combination with

// From Microsoft's Knowledge Base article #140723: 
// http://support.microsoft.com/kb/140723
// "How to force a screen saver to close once started 
// in Windows NT, Windows 2000, and Windows Server 2003"
public static void KillScreenSaver()
{
    IntPtr hDesktop = OpenDesktop("Screen-saver", 0, false, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
    if (hDesktop != IntPtr.Zero)
    {
        if (!EnumDesktopWindows(hDesktop, KillScreenSaverFunc, IntPtr.Zero) || !CloseDesktop(hDesktop))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }
    else
    {
        TerminateWindow(GetForegroundWindow());
    }
}

private static bool KillScreenSaverFunc(IntPtr hWnd, IntPtr lParam)
{
    if (IsWindowVisible(hWnd))
    {
        TerminateWindow(hWnd);
    }

    return true;
}

private static void TerminateWindow(IntPtr hWnd)
{
    if (!PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero))
    {
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }
}

And

public static void ActivateScreensaver()
{
    SetScreenSaverActive(TRUE);
}

private static void SetScreenSaverActive(uint active)
{
    IntPtr nullVar = IntPtr.Zero;

    // Ignoring error since ERROR_OPERATION_IN_PROGRESS is expected.
    // Methode is called to reset timer and to prevent possible errors as mentioned in Microsoft's Knowledge Base article #140723:
    // http://support.microsoft.com/kb/140723
    SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, active, ref nullVar, SPIF_SENDWININICHANGE);
}

Which worked, partially. However, the display was turned off immediately again (as a matter of fact, most times one couldn't even see that the monitor was turned again apart from the power-led going "steady on" instead of "flashing on off = power safe" for a short time).

So i guess i'm missing a crucial part of the picture like reliably resetting system idle timer to make sure the screen is not immediately being turned off, again.

Edit: According to my investigations SetThreadExecutionState(ES_DISPLAY_REQUIRED) seems to do the trick in regards to resetting the idle-timer. However, i still don't know the correct sequence of calls. I'll self-answer when i figure it out...

BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • Have you tried [simulating a mouse move](http://stackoverflow.com/questions/12572441/sendmessage-sc-monitorpower-wont-turn-monitor-on-when-running-windows-8)? – stuartd Mar 29 '16 at 16:08
  • @stuartd thanks for the tip. I've used it (albeit a bit differently - using a newer, non deprecated, API) in my answer. However, by itself it's not sufficient to solve both cases – BatteryBackupUnit Mar 29 '16 at 18:20
  • Nice answer. Can you not just change the screen saver / display settings though? – stuartd Mar 29 '16 at 18:33
  • @stuartd one could, but our application is going to run on a multitude of computers, some are setup by us but some are setup and maintained by the client. Rather then dealing with "bug reports" and "customer feedback" and "installation manuals" i prefer writing software that "just works". You wouldn't want to change power options to run a video player, would you? ;-) (we're not developing a video player...) – BatteryBackupUnit Mar 29 '16 at 18:45
  • Ah I thought this was some kind of kiosk mode. My mistake. – stuartd Mar 29 '16 at 20:00

1 Answers1

2

The following code will:

  • interrupt a running screensaver
  • turn on a turned-off screen ("turn off" as mentioned in power options)
private static readonly ILog Log = LogManager.GetLogger(
    MethodBase.GetCurrentMethod().DeclaringType);

public void TurnOnScreenAndInterruptScreensaver()
{
    TryTurnOnScreenAndResetDisplayIdleTimer();

    TryInterruptScreensaver();
}

/// <summary>
/// Moves the mouse which turns on a turned-off screen and also resets the 
/// display idle timer, which is key, because otherwise the 
/// screen would be turned off again immediately.
/// </summary>
private static void TryTurnOnScreenAndResetDisplayIdleTimer()
{
    var input = new SendInputNativeMethods.Input {
        type = SendInputNativeMethods.SendInputEventType.InputMouse, };
    try
    {
        SendInputNativeMethods.SendInput(input);
    }
    catch (Win32Exception exception)
    {
        Log.Error("Could not send mouse move input to turn on display", exception);
    }
}

private static void TryInterruptScreensaver()
{
    try
    {
        if (ScreensaverNativeMethods.GetScreenSaverRunning())
        {
            ScreensaverNativeMethods.KillScreenSaver();
        }

        // activate screen saver again so that after idle-"timeout" it shows again
        ScreensaverNativeMethods.ActivateScreensaver();
    }
    catch (Win32Exception exception)
    {
        Log.Error("Screensaver could not be deactivated", exception);
    }
}

SendInputNativeMethods:

public static class SendInputNativeMethods
{
    public static void SendInput(params Input[] inputs)
    {
        if (SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<Input>())
           != (uint)inputs.Length)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern uint SendInput(
        uint nInputs,
        [MarshalAs(UnmanagedType.LPArray), In] Input[] pInputs,
        int cbSize);

    [StructLayout(LayoutKind.Sequential)]
    public struct Input
    {
        public SendInputEventType type;
        public MouseKeybdhardwareInputUnion mkhi;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct MouseKeybdhardwareInputUnion
    {
        [FieldOffset(0)]
        public MouseInputData mi;

        [FieldOffset(0)]
        public KEYBDINPUT ki;

        [FieldOffset(0)]
        public HARDWAREINPUT hi;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct KEYBDINPUT
    {
        public ushort wVk;
        public ushort wScan;
        public uint dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct HARDWAREINPUT
    {
        public int uMsg;
        public short wParamL;
        public short wParamH;
    }
    public struct MouseInputData
    {
        public int dx;
        public int dy;
        public uint mouseData;
        public MouseEventFlags dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }
    [Flags]
    public enum MouseEventFlags : uint
    {
        MOUSEEVENTF_MOVE = 0x0001,
        MOUSEEVENTF_LEFTDOWN = 0x0002,
        MOUSEEVENTF_LEFTUP = 0x0004,
        MOUSEEVENTF_RIGHTDOWN = 0x0008,
        MOUSEEVENTF_RIGHTUP = 0x0010,
        MOUSEEVENTF_MIDDLEDOWN = 0x0020,
        MOUSEEVENTF_MIDDLEUP = 0x0040,
        MOUSEEVENTF_XDOWN = 0x0080,
        MOUSEEVENTF_XUP = 0x0100,
        MOUSEEVENTF_WHEEL = 0x0800,
        MOUSEEVENTF_VIRTUALDESK = 0x4000,
        MOUSEEVENTF_ABSOLUTE = 0x8000
    }
    public enum SendInputEventType : int
    {
        InputMouse,
        InputKeyboard,
        InputHardware
    }

ScreensaverNativeMethods:

internal static class ScreensaverNativeMethods
{
    private const int SPI_GETSCREENSAVERRUNNING = 0x0072;
    private const int SPI_SETSCREENSAVEACTIVE = 0x0011;
    private const int SPIF_SENDWININICHANGE = 0x0002;
    private const uint DESKTOP_WRITEOBJECTS = 0x0080;
    private const uint DESKTOP_READOBJECTS = 0x0001;
    private const int WM_CLOSE = 0x0010;
    private const int TRUE = 1;

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SystemParametersInfo(
        uint uiAction,
        uint uiParam,
        ref IntPtr pvParam,
        uint fWinIni);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool PostMessage(
        IntPtr hWnd,
        uint msg,
        IntPtr wParam,
        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,
        uint dwDesiredAccess);

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

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool EnumDesktopWindows(
        IntPtr hDesktop, 
        EnumDesktopWindowsProc callback, 
        IntPtr lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr GetForegroundWindow();

    private delegate bool EnumDesktopWindowsProc(IntPtr hDesktop, IntPtr lParam);

    public static bool GetScreenSaverRunning()
    {
        IntPtr isRunning = IntPtr.Zero;

        if (!SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref isRunning, 0))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        return isRunning != IntPtr.Zero;
    }

    public static void ActivateScreensaver()
    {
        SetScreenSaverActive(TRUE);
    }

    private static void SetScreenSaverActive(uint active)
    {
        IntPtr nullVar = IntPtr.Zero;

        // Ignoring error since ERROR_OPERATION_IN_PROGRESS is expected.
        // Methode is called to reset timer and to prevent possible errors 
        // as mentioned in Microsoft's Knowledge Base article #140723:
        // http://support.microsoft.com/kb/140723
        SystemParametersInfo(
            SPI_SETSCREENSAVEACTIVE,
            active,
            ref nullVar,
            SPIF_SENDWININICHANGE);
    }

    // From Microsoft's Knowledge Base article #140723: 
    // http://support.microsoft.com/kb/140723
    // "How to force a screen saver to close once started 
    // in Windows NT, Windows 2000, and Windows Server 2003"
    public static void KillScreenSaver()
    {
        IntPtr hDesktop = OpenDesktop(
            "Screen-saver", 
            0,
            false,
            DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
        if (hDesktop != IntPtr.Zero)
        {
            if (!EnumDesktopWindows(hDesktop, KillScreenSaverFunc, IntPtr.Zero)
                || !CloseDesktop(hDesktop))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        else
        {
            TerminateWindow(GetForegroundWindow());
        }
    }

    private static bool KillScreenSaverFunc(IntPtr hWnd, IntPtr lParam)
    {
        if (IsWindowVisible(hWnd))
        {
            TerminateWindow(hWnd);
        }

        return true;
    }

    private static void TerminateWindow(IntPtr hWnd)
    {
        if (!PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }
}
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68