Skip to content

Commit 9dac25e

Browse files
committed
update to use shutter
1 parent 53cf2a8 commit 9dac25e

File tree

4 files changed

+103
-82
lines changed

4 files changed

+103
-82
lines changed

Program.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using Scum_Bag.Models;
66
using Scum_Bag.Services;
77
using SharpWebview.Content;
8+
using Shutter;
9+
using Shutter.Abstractions;
810

911
namespace Scum_Bag;
1012

@@ -35,7 +37,8 @@ static void Main()
3537
.AddSingleton<SaveService>()
3638
.AddSingleton<ScreenshotService>()
3739
.AddSingleton<SettingsService>()
38-
.AddSingleton<FileService>();
40+
.AddSingleton<FileService>()
41+
.AddSingleton<IShutterService, ShutterService>();
3942
#if DEBUG
4043
//EmbeddedContent embeddedContent = new(embeddedNamespace: "Scum_Bag");
4144
//builder.SetContentProvider(embeddedContent);

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
![screenshot](screenshot.png)
44

55
## About
6-
Scum Bag is a place to hold all of your save backups. It automatically backs up your save files at a given interval and lets you restore to any point. Powered by [Galdr](https://github.com/rthomasv3/Galdr).
6+
Scum Bag is a bag to hold all of your scummed save/file backups. It automatically backs up your save files at a given interval and lets you restore to any point. Powered by [Galdr.Native](https://github.com/rthomasv3/Galdr).
77

88
### Features
99

@@ -12,8 +12,9 @@ Scum Bag is a place to hold all of your save backups. It automatically backs up
1212
* Only makes a backup if the file(s) actually changed
1313
* Reads steam libraries to find installed games
1414
* Takes a screenshot of the game window when the save file changes
15-
* Requires `flameshot` on Linux and screenshots are only full screen
1615
* Supports both windowed and fullscreen apps on Windows
16+
* Mac and Linux only support fullscreen screenshots
17+
* Works on X11 and Wayland
1718

1819
## Requirements
1920

@@ -22,7 +23,6 @@ Scum Bag is a place to hold all of your save backups. It automatically backs up
2223
* Windows 10
2324
* Requires [Webview2 runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
2425
* Linux and BSD
25-
* Requires `flameshot` for screenshots
2626
* Requires [WebKit2GTK](https://webkitgtk.org/)
2727
* Debian-based
2828
* `apt install libgtk-3-0 libwebkit2gtk-4.1-dev`

Scum Bag.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<ItemGroup>
1111
<PackageReference Include="Galdr.Native" Version="1.0.5" />
1212
<PackageReference Include="Gameloop.Vdf" Version="0.6.2" />
13-
<PackageReference Include="System.Drawing.Common" Version="8.0.3" />
13+
<PackageReference Include="Shutter" Version="1.0.0-beta-1" />
1414
<PackageReference Include="System.Text.Json" Version="9.0.8" />
1515
</ItemGroup>
1616

Services/ScreenshotService.cs

Lines changed: 95 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
4-
using System.Drawing;
5-
using System.Drawing.Imaging;
64
using System.IO;
75
using System.Linq;
86
using System.Runtime.InteropServices;
@@ -11,37 +9,13 @@
119
using Scum_Bag.DataAccess.Data;
1210
using Scum_Bag.DataAccess.Data.Steam;
1311
using Scum_Bag.Models;
12+
using Shutter.Abstractions;
13+
using Shutter.Models;
1414

1515
namespace Scum_Bag.Services;
1616

1717
internal sealed class ScreenshotService
1818
{
19-
#region Native
20-
21-
[StructLayout(LayoutKind.Sequential)]
22-
public struct RECT
23-
{
24-
public int Left;
25-
public int Top;
26-
public int Right;
27-
public int Bottom;
28-
29-
public int Width
30-
{
31-
get { return Right - Left; }
32-
}
33-
34-
public int Height
35-
{
36-
get { return Bottom - Top; }
37-
}
38-
}
39-
40-
[DllImport("user32.dll", SetLastError = true)]
41-
public static extern bool GetWindowRect(IntPtr hwnd, ref RECT rectangle);
42-
43-
#endregion
44-
4519
#region Fields
4620

4721
private static readonly int _headerHeight = 32;
@@ -50,6 +24,7 @@ public int Height
5024
private readonly Config _config;
5125
private readonly GameService _gameService;
5226
private readonly LoggingService _loggingService;
27+
private readonly IShutterService _shutterService;
5328
private readonly Dictionary<string, WatchLocation> _watchers;
5429
private bool _flameshotSetup;
5530
private readonly bool _isInFlatpak;
@@ -60,11 +35,13 @@ public int Height
6035

6136
#region Constructor
6237

63-
public ScreenshotService(Config config, GameService gameService, LoggingService loggingService)
38+
public ScreenshotService(Config config, GameService gameService, LoggingService loggingService,
39+
IShutterService shutterService)
6440
{
6541
_config = config;
6642
_gameService = gameService;
6743
_loggingService = loggingService;
44+
_shutterService = shutterService;
6845
_watchers = new();
6946

7047
_isInFlatpak = File.Exists("/.flatpak-info");
@@ -256,6 +233,10 @@ private void TakeScreenshot(string saveDirectory, string gameDirectory)
256233
{
257234
TakeLinuxScreenshot(saveDirectory);
258235
}
236+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
237+
{
238+
TakeMacScreenshot(saveDirectory);
239+
}
259240
else
260241
{
261242
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeScreenshot)} - Skipping screenshot for platform {RuntimeInformation.OSDescription}");
@@ -275,43 +256,101 @@ private void TakeWindowsScreenshot(string saveDirectory, string gameDirectory)
275256

276257
if (gameProcess != null)
277258
{
278-
RECT rectangle = GetWindowRect(gameProcess.MainWindowHandle);
279-
280-
if (rectangle.Width > 0 && rectangle.Height > 0)
259+
try
281260
{
282-
bool removeWindowBorder = true; // may remove this later...
283-
284-
// need to update these trim sizes to a percentage instead of fixed values
285-
int width = rectangle.Width - (removeWindowBorder ? _shadowSize * 2 : 0);
286-
int height = removeWindowBorder ? rectangle.Height - _headerHeight - _shadowSize : rectangle.Height;
287-
int left = rectangle.Left + (removeWindowBorder ? _shadowSize : 0);
288-
int top = rectangle.Top + (removeWindowBorder ? _headerHeight : 0);
289-
290-
Bitmap bitmap = new Bitmap(width, height);
291-
292-
using (Graphics g = Graphics.FromImage(bitmap))
261+
byte[] imageData = _shutterService.TakeScreenshot(new ScreenshotOptions()
293262
{
294-
g.CopyFromScreen(left, top, 0, 0, bitmap.Size, CopyPixelOperation.SourceCopy);
295-
}
263+
Target = Shutter.Enums.CaptureTarget.Window,
264+
WindowHandle = gameProcess.MainWindowHandle,
265+
IncludeBorder = false,
266+
IncludeShadow = false,
267+
Format = Shutter.Enums.ImageFormat.Jpeg,
268+
JpegQuality = 85,
269+
});
270+
271+
if (imageData != null)
272+
{
273+
string savePath = Path.Combine(saveDirectory, _config.LatestScreenshotName);
296274

297-
string savePath = Path.Combine(saveDirectory, _config.LatestScreenshotName);
275+
if (File.Exists(savePath))
276+
{
277+
File.Delete(savePath);
278+
}
298279

299-
if (File.Exists(savePath))
300-
{
301-
File.Delete(savePath);
280+
File.WriteAllBytes(savePath, imageData);
302281
}
303-
304-
bitmap.Save(savePath, ImageFormat.Jpeg);
282+
}
283+
catch (Exception e)
284+
{
285+
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeWindowsScreenshot)} - Window screenshot failed: {e.ToString()}");
305286
}
306287
}
307288
else
308289
{
309-
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeScreenshot)} - Failed to find game process for directory {gameDirectory}");
290+
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeWindowsScreenshot)} - Failed to find game process for directory {gameDirectory}");
310291
}
311292
}
312293
}
313294

314295
private void TakeLinuxScreenshot(string saveDirectory)
296+
{
297+
try
298+
{
299+
byte[] imageData = _shutterService.TakeScreenshot(new ScreenshotOptions()
300+
{
301+
Target = Shutter.Enums.CaptureTarget.FullScreen,
302+
Format = Shutter.Enums.ImageFormat.Jpeg,
303+
JpegQuality = 85,
304+
});
305+
306+
if (imageData != null)
307+
{
308+
string savePath = Path.Combine(saveDirectory, _config.LatestScreenshotName);
309+
310+
if (File.Exists(savePath))
311+
{
312+
File.Delete(savePath);
313+
}
314+
315+
File.WriteAllBytes(savePath, imageData);
316+
}
317+
}
318+
catch (Exception e)
319+
{
320+
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeLinuxScreenshot)} - Screenshot failed: {e.ToString()}");
321+
}
322+
}
323+
324+
private void TakeMacScreenshot(string saveDirectory)
325+
{
326+
try
327+
{
328+
byte[] imageData = _shutterService.TakeScreenshot(new ScreenshotOptions()
329+
{
330+
Target = Shutter.Enums.CaptureTarget.FullScreen,
331+
Format = Shutter.Enums.ImageFormat.Jpeg,
332+
JpegQuality = 85,
333+
});
334+
335+
if (imageData != null)
336+
{
337+
string savePath = Path.Combine(saveDirectory, _config.LatestScreenshotName);
338+
339+
if (File.Exists(savePath))
340+
{
341+
File.Delete(savePath);
342+
}
343+
344+
File.WriteAllBytes(savePath, imageData);
345+
}
346+
}
347+
catch (Exception e)
348+
{
349+
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeMacScreenshot)} - Screenshot failed: {e.ToString()}");
350+
}
351+
}
352+
353+
private void TakeLinuxScreenshotFlameshot(string saveDirectory)
315354
{
316355
if (IsFlameshotSetup())
317356
{
@@ -373,15 +412,15 @@ private void TakeLinuxScreenshot(string saveDirectory)
373412
{
374413
if (attempt == 3)
375414
{
376-
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeLinuxScreenshot)} - {_flameshotCommand} {arguments}");
415+
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeLinuxScreenshotFlameshot)} - {_flameshotCommand} {arguments}");
377416

378417
if (exitCode != null)
379418
{
380-
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeLinuxScreenshot)} - Screenshot may have failed ({exitCode})");
419+
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeLinuxScreenshotFlameshot)} - Screenshot may have failed ({exitCode})");
381420
}
382421
else
383422
{
384-
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeLinuxScreenshot)} - Flameshot process timed out: killed and screenshot not found");
423+
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeLinuxScreenshotFlameshot)} - Flameshot process timed out: killed and screenshot not found");
385424
}
386425
}
387426
else
@@ -402,31 +441,10 @@ private void TakeLinuxScreenshot(string saveDirectory)
402441
}
403442
else
404443
{
405-
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeLinuxScreenshot)} - flameshot not available");
444+
_loggingService.LogInfo($"{nameof(ScreenshotService)}>{nameof(TakeLinuxScreenshotFlameshot)} - flameshot not available");
406445
}
407446
}
408447

409-
private static RECT GetWindowRect(IntPtr hwnd)
410-
{
411-
RECT rectangle = default;
412-
int attempts = 0;
413-
414-
while (attempts++ < 5)
415-
{
416-
rectangle = new RECT();
417-
GetWindowRect(hwnd, ref rectangle);
418-
419-
if (rectangle.Width > 0 && rectangle.Height > 0)
420-
{
421-
break;
422-
}
423-
424-
Thread.Sleep(50);
425-
}
426-
427-
return rectangle;
428-
}
429-
430448
private HashSet<string> GetExecutables(string directory)
431449
{
432450
HashSet<string> fileSet = new();

0 commit comments

Comments
 (0)