3nb gaming
3nb gaming

Reputation: 21

Enable Screensaver on Windows from WPF application in C# during media playback

I am new to WPF and c#. I'm using in an simple WPF Application the MediaElement to play an looped video, using a Storyboard.

Now in this Project I have a big problem. The playing video in the MediaElement suppresses the screensaver from starting. But in my case, i need the normal windows behavior. (Automatic screensaver and automatic log off and so on.)

How can I make the normal screensaver appearing again, even if an video is playing in my MediaElement?

The Code is simple: The Main Windows:

<Window x:Name="BrutusView" x:Class="BRUTUS_Panel_View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BRUTUS_Panel_View"
mc:Ignorable="d"
        Title="3nb BRUTUS Side Panel Player" Height="320" Width="256" HorizontalAlignment="Center" VerticalAlignment="Center" Left="0" Top="0" Deactivated="BrutusView_Deactivated" LostFocus="BrutusView_LostFocus" Loaded="BrutusView_Loaded" WindowStyle="ToolWindow" ResizeMode="CanResize" Icon="F:\Dokumente\Visual Studio 2015\Projects\BRUTUSConfig\BRUTUSConfig\3nb.ico" Topmost="True" ShowInTaskbar="False" ShowActivated="False" Visibility="Visible">
    <Grid>
        <MediaElement x:Name="myMediaElement" LoadedBehavior="Manual" HorizontalAlignment="Center"  VerticalAlignment="Center" IsMuted="True" Stretch="UniformToFill">
            <MediaElement.Triggers>
                <EventTrigger RoutedEvent="MediaElement.Loaded">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <MediaTimeline Source="C:\Animations\1.mp4" Storyboard.TargetName="myMediaElement" RepeatBehavior="Forever" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </MediaElement.Triggers>
        </MediaElement>
    </Grid>
</Window>

The c#:

using System;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Threading;

namespace BRUTUS_Panel_View
{

    public partial class MainWindow : Window
    {



        public MainWindow()
        {
            InitializeComponent();
        }

        private void BrutusView_Loaded(object sender, RoutedEventArgs e)
        {

        }

        private void BrutusView_Deactivated(object sender, EventArgs e)
        {
            BrutusView.Topmost = true;
        }

        private void BrutusView_LostFocus(object sender, RoutedEventArgs e)
        {
            BrutusView.Topmost = true;
        }

    }
}

Upvotes: 2

Views: 1715

Answers (2)

Philip
Philip

Reputation: 126

I was having a similar issue where a WPF MediaElement was preventing the screensaver from kicking in. Enabling the "Allow screen saver during playback" setting in Windows Media Player Legacy resolved the issue -

Media player settings

Upvotes: 0

Jacek Blaszczynski
Jacek Blaszczynski

Reputation: 3269

Answer contains two solutions: (i) based on UWP DisplayRequest class, (ii) based on custom ScreenSaverManager class which is more general.

1. UWP DisplayRequest class

Windows provides direct method to request suspension of screen saver and calling it successfully guarantees that screen saver will not start due to user inactivity i.e. during media playback. The reverse is possible with method enabling screen saver again. Both methods are available in Windows 10 (UWP Runtime) and can be accessed via Windows.System.Display.DisplayRequest class. Example of how to use it outside of UWP applications - in WPF desktop application - is posted below.

Before working with code it is necessary to add to a standard WPF application created in Visual Studio from template the following references: (i) Windows.winmd from directory: C:\Program Files (x86)\Windows Kits\10\UnionMetadata and (ii) System.Runtime.WindowsRuntime.dll from directory: C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5.1 (or v4.5).

To suspend or enable screen saver during playback hook start and stop of playaback events to relevant methods.

using System;
using System.Windows;
using System.Windows.Controls;
using Windows.System.Display;

namespace SuspendScreenSaverWpf
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private DisplayRequest mDisplayRequest;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void SuspendButton_Click(object sender, RoutedEventArgs e)
        {
            Button b = sender as Button;
            if (b != null)
            {
                try
                {
                    if (mDisplayRequest == null)
                    {
                        // This call creates an instance of the displayRequest object
                        mDisplayRequest = new DisplayRequest();
                    }
                }
                catch (Exception ex)
                {
                    this.MessageBoard.Content = $"Error Creating Display Request: {ex.Message}";
                }

                if (mDisplayRequest != null)
                {
                    try
                    {
                        // This call activates a display-required request. If successful,
                        // the screen is guaranteed not to turn off automatically due to user inactivity.
                        mDisplayRequest.RequestActive();
                        this.MessageBoard.Content = $"Display request activated - ScreenSaver suspended";
                        this.EnableButton.IsEnabled = true;
                        this.SuspendButton.IsEnabled = false;
                    }
                    catch (Exception ex)
                    {
                        this.MessageBoard.Content = $"Error: {ex.Message}";
                    }
                }
            }
        }

        private void EnableButton_Click(object sender, RoutedEventArgs e)
        {
            Button b = sender as Button;
            if (b != null)
            {
                if (mDisplayRequest != null)
                {
                    try
                    {
                        // This call de-activates the display-required request. If successful, the screen
                        // might be turned off automatically due to a user inactivity, depending on the
                        // power policy settings of the system. The requestRelease method throws an exception
                        // if it is called before a successful requestActive call on this object.
                        mDisplayRequest.RequestRelease();
                        this.MessageBoard.Content = $"Display request released - ScreenSaver enabled.";
                        this.SuspendButton.IsEnabled = true;
                        this.EnableButton.IsEnabled = false;
                    }
                    catch (Exception ex)
                    {
                        this.MessageBoard.Content = $"Error: {ex.Message}";
                    }
                }
            }
        }
    }
}



<Window x:Class="SuspendScreenSaverWpf.MainWindow"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                    xmlns:local="clr-namespace:SuspendScreenSaverWpf"
                    mc:Ignorable="d"
                    Title="MainWindow ScreenSaver management demo" Height="350" Width="525">
    <Grid>
        <Button x:Name="SuspendButton" IsEnabled="true" Content="Suspend ScreenSaver" HorizontalAlignment="Left" Margin="73,250,0,0" VerticalAlignment="Top" Width="150" Click="SuspendButton_Click"/>
        <Button x:Name="EnableButton" IsEnabled="False" Content="Enable ScreenSaver" HorizontalAlignment="Left" Margin="298,250,0,0" VerticalAlignment="Top" Width="150" Click="EnableButton_Click"/>
        <Label x:Name="MessageBoard" Content="Example project demonstrating how to disable ScreenSaver on Windows 10" HorizontalAlignment="Left" Height="78" Margin="73,39,0,0" VerticalAlignment="Top"  Width="375"/>

    </Grid>
</Window>

Another instruction on referencing assemblies in order to access Windows 10 UWP APIs can be found on a web page of UwpDesktop NuGet package. The UwpDesktop package itself seems to be not maintained for duration of at least last two Windows 10 Creators updates but could be still useful if targeting 10.0.14393 APIs.

For examples of how to use this method from C/C++ code see the repo of SDL Library

2. Custom ScreenSaverManager class

Challenging part of the question is related to undocumented behavior of System.Windows.Controls.MediaElement control which suspends screen saver for the duration of media playback. Despite the fact that preventing activation of screen saver is a good practice from the perspective of user watching movie there are applications where it is not desirable. Obligatory disabling of screen saver or workstation lock out due to security reasons or when playing media is not most important application function is not a good practice. Microsoft should provide a public property o method which could be used to control this functionality.

To avoid any hacks I have tried to solve problem using only Win32 and .NET/WPF public APIs. The simplest and yet most effective solution is based on creating our own ScreenSaverManager which duplicates OS functionality and enforces on demand original screen saver behavior (automatic screensaver and automatic log off and so on). First ScreenSaverManager reads current session settings and based on them starts enforcing session policy bypassing methods used to block screen saver and session lock out by MediaElement.

using System;
using System.Timers;

namespace ManageScreenSaver.MediaElementWpf
{
    public class ScreenSaverManager
    {
        private static ScreenSaverManager _Manager;

        public static ScreenSaverManager Instance
        {
            get
            {
                if (_Manager != null)
                    return _Manager;

                _Manager = new ScreenSaverManager();
                return _Manager;
            }
        }

        private TimeSpan _ScreenSaverTimeout;
        private bool _IsScreenSaverSecure;
        private Timer _Timer;

        protected ScreenSaverManager()
        {
            _ScreenSaverTimeout = NativeMethods.ScreenSaverTimeout;
            _IsScreenSaverSecure = NativeMethods.IsScreenSaverSecure;
            _Timer = new Timer(_ScreenSaverTimeout.TotalMilliseconds/2);
            _Timer.AutoReset = false;
            _Timer.Elapsed += Timer_Elapsed;
            _Timer.Start();
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            var lastInput = NativeMethods.GetLastUserInputTimeInterval();
            MainWindow.Console.WriteLine($"Last user input interval: {lastInput}");
            if (lastInput >= _ScreenSaverTimeout)
            {
                StartScreenSaver();
            }
            else
            {
                _Timer.Interval = _ScreenSaverTimeout.Subtract(lastInput).TotalMilliseconds + 100;
                _Timer.Start();
            }
        }

        private void StartScreenSaver()
        {
            if (_IsScreenSaverSecure)
            {
                NativeMethods.LockWorkStationSession();
            }
            else
            {
                var result = NativeMethods.SendMessage((IntPtr) 0xffff, (uint) WindowMessage.WM_SYSCOMMAND, (uint) WmSysCommandParam.ScSCREENSAVE, 0);
            }
        }
    }
}

Interop methods are defined in separate class NativeMethods:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace ManageScreenSaver.MediaElementWpf
{
    public static class NativeMethods
    {
        public const uint SPI_GETSCREENSAVETIMEOUT = 0x000E;
        public const uint SPI_SETSCREENSAVETIMEOUT = 0x000F;
        public const uint SPI_GETSCREENSAVEACTIVE = 0x0010;
        public const uint SPI_SETSCREENSAVEACTIVE = 0x0011;
        public const uint SPI_SETSCREENSAVERRUNNING = 0x0061;
        public const uint SPI_SCREENSAVERRUNNING = SPI_SETSCREENSAVERRUNNING;
        public const uint SPI_GETSCREENSAVERRUNNING = 0x0072;
        public const uint SPI_GETSCREENSAVESECURE = 0x0076;
        public const uint SPI_SETSCREENSAVESECURE = 0x0077;

        public const uint SPIF_UPDATEINIFILE = 0x0001;
        public const uint SPIF_SENDWININICHANGE = 0x0002;
        public const uint SPIF_SENDCHANGE = SPIF_SENDWININICHANGE;

        [DllImport("user32.dll", CallingConvention = CallingConvention.Winapi, PreserveSig = true, SetLastError = true)]
        internal static unsafe extern bool SystemParametersInfo(uint  uiAction, uint  uiParam, void* pvParam, uint  fWinIni);

        [DllImport("user32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true)]
        internal static extern IntPtr DefWindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled);

        [DllImport("user32.dll", SetLastError = true)]
        static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

        [DllImport("User32.dll", SetLastError = true)]
        internal static extern int SendMessage(IntPtr hWnd, uint msg, uint wParam, uint lParam);

        [DllImport("user32.dll", SetLastError = true)]
        internal static extern bool LockWorkStation();

        public static TimeSpan GetLastUserInputTimeInterval()
        {
            LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
            lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);

            if (!GetLastInputInfo(ref lastInputInfo))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            uint ticks = (uint)Environment.TickCount;
            var idleMiliseconds = ticks - lastInputInfo.dwTime;
            return idleMiliseconds > 0 ? TimeSpan.FromMilliseconds((double)idleMiliseconds) : default(TimeSpan);
        }


        public static void LockWorkStationSession()
        {
            if (!LockWorkStation())
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }

        public static bool IsScreenSaverActive
        {
            get
            {
                bool enabled = false;
                unsafe
                {
                    var result = SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &enabled, 0);
                    if (!result)
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                    return enabled;
                }
            }
        }

        public static bool IsScreenSaverRunning
        {
            get
            {
                bool enabled = false;
                unsafe
                {
                    var result = SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &enabled, 0);
                    if (!result)
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                    return enabled;
                }
            }
        }

        public static bool IsScreenSaverSecure
        {
            get
            {
                bool enabled = false;
                unsafe
                {
                    var result = SystemParametersInfo(SPI_GETSCREENSAVESECURE, 0, &enabled, 0);
                    if (!result)
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                    return enabled;
                }
            }
        }

        public static TimeSpan ScreenSaverTimeout
        {
            get
            {
                int timeout = 0;
                unsafe
                {
                    var result = SystemParametersInfo(SPI_GETSCREENSAVETIMEOUT, 0, &timeout, 0);
                    if (!result)
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                    return TimeSpan.FromSeconds(timeout);
                }
            }
        }
    }

    [Flags]
    public enum WindowMessage : uint
    {
        WM_COMMAND = 0x0111,
        WM_SYSCOMMAND = 0x0112, 
    }

    public enum WmSysCommandParam : uint
    {
        ScSIZE        = 0xF000,
        ScMOVE        = 0xF010,
        ScMINIMIZE    = 0xF020,
        ScMAXIMIZE    = 0xF030,
        ScNEXTWINDOW  = 0xF040,
        ScPREVWINDOW  = 0xF050,
        ScCLOSE       = 0xF060,
        ScVSCROLL     = 0xF070,
        ScHSCROLL     = 0xF080,
        ScMOUSEMENU   = 0xF090,
        ScKEYMENU     = 0xF100,
        ScARRANGE     = 0xF110,
        ScRESTORE     = 0xF120,
        ScTASKLIST    = 0xF130,
        ScSCREENSAVE  = 0xF140,
        ScHOTKEY      = 0xF150,
        ScDEFAULT     = 0xF160,
        ScMONITORPOWER= 0xF170,
        ScCONTEXTHELP = 0xF180,
        ScSEPARATOR   = 0xF00F,
    }
 }   

Finally there is example on how to use ScreenSaverManager in WPF application:

<Window x:Class="ManageScreenSaver.MediaElementWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ManageScreenSaver.MediaElementWpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="570" Width="550" MinHeight="570" MinWidth="550" MaxHeight="570" MaxWidth="550">
    <Grid Margin="0,0,0,0">
        <MediaElement x:Name="myMediaElement" Width="530" Height="270" LoadedBehavior="Manual" HorizontalAlignment="Center"  VerticalAlignment="Center" IsMuted="True" Stretch="Fill" Margin="10,52,10,197" >
            <MediaElement.Triggers>
                <EventTrigger RoutedEvent="MediaElement.Loaded">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <MediaTimeline Source="..\..\BigBuckBunny_320x180.mp4" Storyboard.TargetName="myMediaElement" RepeatBehavior="Forever" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </MediaElement.Triggers>
        </MediaElement>
        <Button x:Name="SuspendButton" IsEnabled="true" Content="Suspend ScreenSaver" HorizontalAlignment="Left" Margin="74,489,0,0" VerticalAlignment="Top" Width="150" Click="SuspendButton_Click" RenderTransformOrigin="0.501,2.334"/>
        <Button x:Name="EnableButton" IsEnabled="true" Content="Enable ScreenSaver" HorizontalAlignment="Left" Margin="302,489,0,0" VerticalAlignment="Top" Width="150" Click="EnableButton_Click" RenderTransformOrigin="0.508,1.359"/>
        <Label x:Name="MessageBoard" Content="Example project demonstrating how to disable ScreenSaver on Windows 10" HorizontalAlignment="Left" Height="25" Margin="44,10,0,0" VerticalAlignment="Top"  Width="432"/>
        <TextBox x:Name="TextBox" Text="" HorizontalAlignment="Center" HorizontalContentAlignment="Left" Height="110" Margin="10,342,10,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="510"/>
    </Grid>
</Window>

using System;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using Windows.System.Display;

namespace ManageScreenSaver.MediaElementWpf
{
    public partial class MainWindow : Window
    {
        private DisplayRequest mDisplayRequest;
        internal static TextBoxWriter Console;
        private static ScreenSaverManager _ScreenSaverManager;

        public MainWindow()
        {
            InitializeComponent();
            Console = new TextBoxWriter(this.TextBox);
            _ScreenSaverManager = ScreenSaverManager.Instance;
            PrintSSaverStatus(" MainWindow.ctor");
        }

        private void PrintSSaverStatus(string apendedText = "")
        {
            Console.WriteLine(GetScreenSaverStatusMessage() + apendedText);
        }

        private void SuspendButton_Click(object sender, RoutedEventArgs e)
        {
            Button b = sender as Button;
            if (b != null)
            {
                EnsureDisplayRequest();

                if (mDisplayRequest != null)
                {
                    try
                    {
                        // This call activates a display-required request. If successful,
                        // the screen is guaranteed not to turn off automatically due to user inactivity.
                        mDisplayRequest.RequestActive();
                        this.MessageBoard.Content = $"Display request activated - ScreenSaver suspended";
                        this.EnableButton.IsEnabled = true;
                        this.SuspendButton.IsEnabled = false;
                    }
                    catch (Exception ex)
                    {
                        this.MessageBoard.Content = $"Error: {ex.Message}";
                    }

                    PrintSSaverStatus(" SuspendButton_Click");
                }
            }
        }

        private void EnsureDisplayRequest()
        {
            try
            {
                if (mDisplayRequest == null)
                {
                    // This call creates an instance of the displayRequest object
                    mDisplayRequest = new DisplayRequest();
                }
            }
            catch (Exception ex)
            {
                this.MessageBoard.Content = $"Error Creating Display Request: {ex.Message}";
            }
        }

        private void EnableButton_Click(object sender, RoutedEventArgs e)
        {
            Button b = sender as Button;
            if (b != null)
            {
                EnsureDisplayRequest();

                if (mDisplayRequest != null)
                {
                    try
                    {
                        // This call de-activates the display-required request. If successful, the screen
                        // might be turned off automatically due to a user inactivity, depending on the
                        // power policy settings of the system. The requestRelease method throws an exception
                        // if it is called before a successful requestActive call on this object.
                        mDisplayRequest.RequestRelease();
                        this.MessageBoard.Content = $"Display request released - ScreenSaver enabled.";
                        this.SuspendButton.IsEnabled = true;
                        this.EnableButton.IsEnabled = false;
                    }
                    catch (Exception ex)
                    {
                        this.MessageBoard.Content = $"Error: {ex.Message}";
                    }

                    PrintSSaverStatus(" EnableButton_Click");
                }
            }
        }

        private string GetScreenSaverStatusMessage()
        {
            string message = $"Screen Saver is: \"{{0}}\", \"{{1}}\", timeout: \"{{2}}\"  {DateTime.UtcNow}";
            message = String.Format(message,
                NativeMethods.IsScreenSaverActive ? "active" : "inactive",
                NativeMethods.IsScreenSaverSecure ? "secure" : "not secure",
                NativeMethods.ScreenSaverTimeout);
            return message;
        }
    }
}

There is still small utility missing which is WPF console:

using System;
using System.IO;
using System.Text;
using System.Windows.Controls;

namespace ManageScreenSaver.MediaElementWpf
{
    public class TextBoxWriter : TextWriter
    {

        private TextBox _TextBox;
        private string _NewLine = "\n";

        public TextBoxWriter(TextBox target)
        {
            if (target == null)
                throw new ArgumentNullException(nameof(target));

            _TextBox = target;
        }

        public override Encoding Encoding => new UTF8Encoding(false);

        public override string NewLine { get => _NewLine; set => _NewLine = value; }

        public override void Write(string value)
        {
            _TextBox.Dispatcher.InvokeAsync(
                () => _TextBox.AppendText(value)
                );
        }

        public override void WriteLine(string value)
        {
            _TextBox.Dispatcher.InvokeAsync(
                () => _TextBox.AppendText(value + NewLine)
                );
        }
    }
}

Beware: this is not production ready code. It is necessary to implement error handling, system and monitor power events handling, login and logout handling: workstation session start and end, in session screen saver configuration changes.

This method has some limitations and will not work when connected via RDP to remote machine but will work when connected via console to Windows VM in Hyper-V.

Upvotes: 2

Related Questions