2019-01-24 08:43:03 +00:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 09:19:50 +00:00
2022-06-17 07:37:17 +00:00
#nullable disable
2018-03-21 03:29:44 +00:00
using System ;
2018-04-13 12:13:09 +00:00
using System.Threading ;
using System.Threading.Tasks ;
2018-03-13 21:17:12 +00:00
using osu.Framework.Allocation ;
2018-03-22 11:35:07 +00:00
using osu.Framework.Audio ;
using osu.Framework.Audio.Sample ;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables ;
2020-02-06 20:02:03 +00:00
using osu.Framework.Graphics ;
2018-03-14 19:55:24 +00:00
using osu.Framework.Input ;
using osu.Framework.Input.Bindings ;
2021-09-16 09:26:12 +00:00
using osu.Framework.Input.Events ;
2018-03-13 21:17:12 +00:00
using osu.Framework.Platform ;
2018-04-13 12:13:09 +00:00
using osu.Framework.Threading ;
2018-03-13 21:17:12 +00:00
using osu.Game.Configuration ;
2018-03-14 19:55:24 +00:00
using osu.Game.Input.Bindings ;
2018-03-16 18:25:00 +00:00
using osu.Game.Overlays ;
using osu.Game.Overlays.Notifications ;
2018-08-17 05:30:44 +00:00
using SixLabors.ImageSharp ;
2020-07-24 06:00:18 +00:00
using SixLabors.ImageSharp.Formats.Jpeg ;
2018-04-13 09:19:50 +00:00
2018-03-13 21:17:12 +00:00
namespace osu.Game.Graphics
{
2020-02-06 20:22:30 +00:00
public class ScreenshotManager : Component , IKeyBindingHandler < GlobalAction > , IHandleGlobalKeyboardInput
2018-03-13 21:17:12 +00:00
{
2018-04-13 12:13:09 +00:00
private readonly BindableBool cursorVisibility = new BindableBool ( true ) ;
/// <summary>
2018-04-13 12:15:08 +00:00
/// Changed when screenshots are being or have finished being taken, to control whether cursors should be visible.
2018-04-13 12:13:09 +00:00
/// If cursors should not be visible, cursors have 3 frames to hide themselves.
/// </summary>
public IBindable < bool > CursorVisibility = > cursorVisibility ;
2018-03-13 21:17:12 +00:00
private Bindable < ScreenshotFormat > screenshotFormat ;
2018-04-13 12:13:09 +00:00
private Bindable < bool > captureMenuCursor ;
2020-02-14 13:14:00 +00:00
[Resolved]
private GameHost host { get ; set ; }
2018-03-13 21:17:12 +00:00
private Storage storage ;
2020-02-14 13:14:00 +00:00
[Resolved]
2022-04-18 10:59:57 +00:00
private INotificationOverlay notificationOverlay { get ; set ; }
2018-04-13 09:19:50 +00:00
2021-01-19 08:11:40 +00:00
private Sample shutter ;
2018-04-13 09:19:50 +00:00
2018-03-13 21:17:12 +00:00
[BackgroundDependencyLoader]
2020-02-14 13:14:00 +00:00
private void load ( OsuConfigManager config , Storage storage , AudioManager audio )
2018-03-13 21:17:12 +00:00
{
this . storage = storage . GetStorageForDirectory ( @"screenshots" ) ;
2018-04-13 09:19:50 +00:00
2018-03-13 21:17:12 +00:00
screenshotFormat = config . GetBindable < ScreenshotFormat > ( OsuSetting . ScreenshotFormat ) ;
2018-04-13 12:13:09 +00:00
captureMenuCursor = config . GetBindable < bool > ( OsuSetting . ScreenshotCaptureMenuCursor ) ;
2018-04-13 09:19:50 +00:00
2019-05-28 08:06:01 +00:00
shutter = audio . Samples . Get ( "UI/shutter" ) ;
2018-03-13 21:17:12 +00:00
}
2018-04-13 09:19:50 +00:00
2021-09-16 09:26:12 +00:00
public bool OnPressed ( KeyBindingPressEvent < GlobalAction > e )
2018-03-14 19:55:24 +00:00
{
2021-11-18 03:35:47 +00:00
if ( e . Repeat )
return false ;
2021-09-16 09:26:12 +00:00
switch ( e . Action )
2018-03-14 19:55:24 +00:00
{
case GlobalAction . TakeScreenshot :
2018-03-22 11:35:07 +00:00
shutter . Play ( ) ;
2018-03-19 19:39:00 +00:00
TakeScreenshotAsync ( ) ;
2018-03-14 19:55:24 +00:00
return true ;
}
2018-04-13 09:19:50 +00:00
2018-03-14 19:55:24 +00:00
return false ;
}
2018-04-13 09:19:50 +00:00
2021-09-16 09:26:12 +00:00
public void OnReleased ( KeyBindingReleaseEvent < GlobalAction > e )
2020-01-22 04:22:34 +00:00
{
}
2018-04-13 09:19:50 +00:00
2018-04-13 12:13:09 +00:00
private volatile int screenShotTasks ;
2018-08-29 11:57:48 +00:00
public Task TakeScreenshotAsync ( ) = > Task . Run ( async ( ) = >
2018-03-13 21:17:12 +00:00
{
2018-04-13 12:13:09 +00:00
Interlocked . Increment ( ref screenShotTasks ) ;
if ( ! captureMenuCursor . Value )
{
cursorVisibility . Value = false ;
// We need to wait for at most 3 draw nodes to be drawn, following which we can be assured at least one DrawNode has been generated/drawn with the set value
const int frames_to_wait = 3 ;
int framesWaited = 0 ;
2019-09-14 14:08:56 +00:00
using ( var framesWaitedEvent = new ManualResetEventSlim ( false ) )
{
ScheduledDelegate waitDelegate = host . DrawThread . Scheduler . AddDelayed ( ( ) = >
{
2020-01-27 03:16:00 +00:00
if ( framesWaited + + > = frames_to_wait )
2019-09-14 14:08:56 +00:00
// ReSharper disable once AccessToDisposedClosure
framesWaitedEvent . Set ( ) ;
} , 10 , true ) ;
2022-06-23 06:28:20 +00:00
if ( ! framesWaitedEvent . Wait ( 1000 ) )
throw new TimeoutException ( "Screenshot data did not arrive in a timely fashion" ) ;
2019-09-14 14:08:56 +00:00
waitDelegate . Cancel ( ) ;
}
2018-04-13 12:13:09 +00:00
}
2021-03-08 03:57:16 +00:00
using ( var image = await host . TakeScreenshotAsync ( ) . ConfigureAwait ( false ) )
2018-03-13 21:17:12 +00:00
{
2019-07-31 22:35:42 +00:00
if ( Interlocked . Decrement ( ref screenShotTasks ) = = 0 & & cursorVisibility . Value = = false )
cursorVisibility . Value = true ;
2018-04-13 12:13:09 +00:00
2022-02-17 17:43:36 +00:00
host . GetClipboard ( ) ? . SetImage ( image ) ;
2021-10-26 05:05:07 +00:00
string filename = getFilename ( ) ;
2018-04-13 09:19:50 +00:00
2021-10-26 05:05:07 +00:00
if ( filename = = null ) return ;
2018-04-13 09:19:50 +00:00
2022-05-16 09:03:53 +00:00
using ( var stream = storage . CreateFileSafely ( filename ) )
2018-03-13 21:17:12 +00:00
{
2021-10-26 05:05:07 +00:00
switch ( screenshotFormat . Value )
{
case ScreenshotFormat . Png :
await image . SaveAsPngAsync ( stream ) . ConfigureAwait ( false ) ;
break ;
2019-04-01 03:44:46 +00:00
2021-10-26 05:05:07 +00:00
case ScreenshotFormat . Jpg :
const int jpeg_quality = 92 ;
2020-07-24 06:26:45 +00:00
2021-10-26 05:05:07 +00:00
await image . SaveAsJpegAsync ( stream , new JpegEncoder { Quality = jpeg_quality } ) . ConfigureAwait ( false ) ;
break ;
2019-04-01 03:44:46 +00:00
2021-10-26 05:05:07 +00:00
default :
throw new InvalidOperationException ( $"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}." ) ;
}
2018-03-13 21:17:12 +00:00
}
2018-04-13 09:19:50 +00:00
2018-03-22 11:44:00 +00:00
notificationOverlay . Post ( new SimpleNotification
{
2021-10-26 05:05:07 +00:00
Text = $"{filename} saved!" ,
2018-03-22 11:44:00 +00:00
Activated = ( ) = >
{
2021-10-26 05:05:07 +00:00
storage . PresentFileExternally ( filename ) ;
2018-03-22 11:44:00 +00:00
return true ;
}
} ) ;
2018-03-19 19:39:00 +00:00
}
2018-04-13 12:13:09 +00:00
} ) ;
2021-10-26 05:05:07 +00:00
private string getFilename ( )
2018-03-16 18:05:25 +00:00
{
2018-03-21 15:27:08 +00:00
var dt = DateTime . Now ;
2021-10-27 04:04:41 +00:00
string fileExt = screenshotFormat . ToString ( ) . ToLowerInvariant ( ) ;
2018-04-13 09:19:50 +00:00
2021-10-27 04:04:41 +00:00
string withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}" ;
2018-03-16 18:05:25 +00:00
if ( ! storage . Exists ( withoutIndex ) )
2018-03-16 18:25:00 +00:00
return withoutIndex ;
2018-04-13 09:19:50 +00:00
2018-03-16 18:05:25 +00:00
for ( ulong i = 1 ; i < ulong . MaxValue ; i + + )
{
2021-10-27 04:04:41 +00:00
string indexedName = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}-{i}.{fileExt}" ;
2018-03-16 18:05:25 +00:00
if ( ! storage . Exists ( indexedName ) )
2018-03-16 18:25:00 +00:00
return indexedName ;
2018-03-16 18:05:25 +00:00
}
2018-04-13 09:19:50 +00:00
2018-03-22 11:45:26 +00:00
return null ;
2018-03-16 18:05:25 +00:00
}
2018-03-13 21:17:12 +00:00
}
}