Skip to content

Commit d280559

Browse files
authored
feat: Added BeforeSend for Screenshot Capture (#2428)
1 parent 871125d commit d280559

File tree

7 files changed

+335
-55
lines changed

7 files changed

+335
-55
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
## Unreleased
44

5+
### Breaking Changes
6+
7+
- `SetBeforeCaptureScreenshot` signature changed from `Func<bool>` to `Func<SentryEvent, bool>`, now receiving the event that
8+
triggered the screenshot capture. This allows context-aware decisions before capture begins. ([#2428](https://github.com/getsentry/sentry-unity/pull/2428))
9+
10+
### Features
11+
12+
- Added `SetBeforeSendScreenshot(Func<Texture2D, SentryEvent, Texture2D?>)` callback that provides the captured screenshot as a
13+
`Texture2D` before JPEG compression. ([#2428](https://github.com/getsentry/sentry-unity/pull/2428))
14+
This enables:
15+
- **Modifying** the screenshot in-place (e.g., blurring sensitive UI areas, redacting PII)
16+
- **Replacing** the screenshot with a different `Texture2D`
17+
- **Discarding** the screenshot by returning `null`
18+
- Access to the event context for conditional processing
19+
520
### Dependencies
621

722
- Bump Cocoa SDK from v8.57.2 to v8.57.3 ([#2424](https://github.com/getsentry/sentry-unity/pull/2424))

src/Sentry.Unity/ScreenshotEventProcessor.cs

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,53 @@ public SentryEvent Process(SentryEvent @event)
2727
if (Interlocked.CompareExchange(ref _isCapturingScreenshot, 1, 0) == 0)
2828
{
2929
_options.LogDebug("Starting coroutine to capture a screenshot.");
30-
_sentryMonoBehaviour.QueueCoroutine(CaptureScreenshotCoroutine(@event.EventId));
30+
_sentryMonoBehaviour.QueueCoroutine(CaptureScreenshotCoroutine(@event));
3131
}
3232

3333
return @event;
3434
}
3535

36-
internal IEnumerator CaptureScreenshotCoroutine(SentryId eventId)
36+
internal IEnumerator CaptureScreenshotCoroutine(SentryEvent @event)
3737
{
3838
_options.LogDebug("Screenshot capture triggered. Waiting for End of Frame.");
3939

4040
// WaitForEndOfFrame does not work in headless mode so we're making it configurable for CI.
4141
// See https://docs.unity3d.com/6000.1/Documentation/ScriptReference/WaitForEndOfFrame.html
4242
yield return WaitForEndOfFrame();
4343

44+
Texture2D? screenshot = null;
4445
try
4546
{
46-
if (_options.BeforeCaptureScreenshotInternal?.Invoke() is false)
47+
if (_options.BeforeCaptureScreenshotInternal?.Invoke(@event) is false)
4748
{
4849
yield break;
4950
}
5051

51-
var screenshotBytes = CaptureScreenshot(_options);
52-
if (screenshotBytes.Length == 0)
52+
screenshot = CreateNewScreenshotTexture2D(_options);
53+
54+
if (_options.BeforeSendScreenshotInternal != null)
55+
{
56+
var modifiedScreenshot = _options.BeforeSendScreenshotInternal(screenshot, @event);
57+
58+
if (modifiedScreenshot == null)
59+
{
60+
_options.LogInfo("Screenshot discarded by BeforeSendScreenshot callback.");
61+
yield break;
62+
}
63+
64+
// Clean up - If the user returned a new texture object and did not modify the passed in one
65+
if (modifiedScreenshot != screenshot)
66+
{
67+
_options.LogDebug("Applying modified screenshot.");
68+
UnityEngine.Object.Destroy(screenshot);
69+
screenshot = modifiedScreenshot;
70+
}
71+
}
72+
73+
var screenshotBytes = screenshot.EncodeToJPG(_options.ScreenshotCompression);
74+
if (screenshotBytes is null || screenshotBytes.Length == 0)
5375
{
54-
_options.LogWarning("Screenshot capture returned empty data for event {0}", eventId);
76+
_options.LogWarning("Screenshot capture returned empty data for event {0}", @event.EventId);
5577
yield break;
5678
}
5779

@@ -61,9 +83,9 @@ internal IEnumerator CaptureScreenshotCoroutine(SentryId eventId)
6183
"screenshot.jpg",
6284
"image/jpeg");
6385

64-
_options.LogDebug("Screenshot captured for event {0}", eventId);
86+
_options.LogDebug("Screenshot captured for event {0}", @event.EventId);
6587

66-
CaptureAttachment(eventId, attachment);
88+
CaptureAttachment(@event.EventId, attachment);
6789
}
6890
catch (Exception e)
6991
{
@@ -72,11 +94,16 @@ internal IEnumerator CaptureScreenshotCoroutine(SentryId eventId)
7294
finally
7395
{
7496
Interlocked.Exchange(ref _isCapturingScreenshot, 0);
97+
98+
if (screenshot != null)
99+
{
100+
UnityEngine.Object.Destroy(screenshot);
101+
}
75102
}
76103
}
77104

78-
internal virtual byte[] CaptureScreenshot(SentryUnityOptions options)
79-
=> SentryScreenshot.Capture(options);
105+
internal virtual Texture2D CreateNewScreenshotTexture2D(SentryUnityOptions options)
106+
=> SentryScreenshot.CreateNewScreenshotTexture2D(options);
80107

81108
internal virtual void CaptureAttachment(SentryId eventId, SentryAttachment attachment)
82109
=> (Sentry.SentrySdk.CurrentHub as Hub)?.CaptureAttachment(eventId, attachment);

src/Sentry.Unity/SentryScreenshot.cs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Sentry.Extensibility;
12
using UnityEngine;
23

34
namespace Sentry.Unity;
@@ -15,11 +16,11 @@ internal static int GetTargetResolution(ScreenshotQuality quality)
1516
};
1617
}
1718

18-
public static byte[] Capture(SentryUnityOptions options) =>
19-
Capture(options, Screen.width, Screen.height);
19+
public static Texture2D CreateNewScreenshotTexture2D(SentryUnityOptions options) =>
20+
CreateNewScreenshotTexture2D(options, Screen.width, Screen.height);
2021

2122
// For testing
22-
internal static byte[] Capture(SentryUnityOptions options, int width, int height)
23+
internal static Texture2D CreateNewScreenshotTexture2D(SentryUnityOptions options, int width, int height)
2324
{
2425
// Make sure the screenshot size does not exceed the target size by scaling the image while conserving the
2526
// original ratio based on which, width or height, is the smaller
@@ -36,15 +37,14 @@ internal static byte[] Capture(SentryUnityOptions options, int width, int height
3637
}
3738
}
3839

39-
Texture2D? screenshot = null;
4040
RenderTexture? renderTextureFull = null;
4141
RenderTexture? renderTextureResized = null;
4242
var previousRenderTexture = RenderTexture.active;
4343

4444
try
4545
{
4646
// Captures the current screenshot synchronously.
47-
screenshot = new Texture2D(width, height, TextureFormat.RGB24, false);
47+
var screenshot = new Texture2D(width, height, TextureFormat.RGB24, false);
4848
renderTextureFull = RenderTexture.GetTemporary(Screen.width, Screen.height);
4949
ScreenCapture.CaptureScreenshotIntoRenderTexture(renderTextureFull);
5050
renderTextureResized = RenderTexture.GetTemporary(width, height);
@@ -66,12 +66,9 @@ internal static byte[] Capture(SentryUnityOptions options, int width, int height
6666
screenshot.ReadPixels(new Rect(0, 0, width, height), 0, 0);
6767
screenshot.Apply();
6868

69-
var bytes = screenshot.EncodeToJPG(options.ScreenshotCompression);
69+
options.LogDebug("Screenshot captured at {0}x{1}.", width, height);
7070

71-
options.DiagnosticLogger?.Log(SentryLevel.Debug,
72-
"Screenshot captured at {0}x{1}: {2} bytes", null, width, height, bytes.Length);
73-
74-
return bytes;
71+
return screenshot;
7572
}
7673
finally
7774
{
@@ -86,11 +83,6 @@ internal static byte[] Capture(SentryUnityOptions options, int width, int height
8683
{
8784
RenderTexture.ReleaseTemporary(renderTextureResized);
8885
}
89-
90-
if (screenshot)
91-
{
92-
Object.Destroy(screenshot);
93-
}
9486
}
9587
}
9688
}

src/Sentry.Unity/SentryUnityOptions.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,7 @@ public sealed class SentryUnityOptions : SentryOptions
252252
/// </summary>
253253
public new StackTraceMode StackTraceMode { get; private set; }
254254

255-
private Func<bool>? _beforeCaptureScreenshot;
256-
257-
internal Func<bool>? BeforeCaptureScreenshotInternal => _beforeCaptureScreenshot;
255+
internal Func<SentryEvent, bool>? BeforeCaptureScreenshotInternal { get; private set; }
258256

259257
/// <summary>
260258
/// Configures a callback function to be invoked before capturing and attaching a screenshot to an event.
@@ -263,9 +261,24 @@ public sealed class SentryUnityOptions : SentryOptions
263261
/// This callback will get invoked right before a screenshot gets taken. If the screenshot should not
264262
/// be taken return `false`.
265263
/// </remarks>
266-
public void SetBeforeCaptureScreenshot(Func<bool> beforeAttachScreenshot)
264+
public void SetBeforeCaptureScreenshot(Func<SentryEvent, bool> beforeAttachScreenshot)
265+
{
266+
BeforeCaptureScreenshotInternal = beforeAttachScreenshot;
267+
}
268+
269+
internal Func<Texture2D, SentryEvent, Texture2D?>? BeforeSendScreenshotInternal { get; private set; }
270+
271+
/// <summary>
272+
/// Configures a callback to modify or discard screenshots before they are sent.
273+
/// </summary>
274+
/// <remarks>
275+
/// This callback receives the captured screenshot as a Texture2D before JPEG compression.
276+
/// You can modify the texture (blur areas, redact PII, etc.) and return it, or return null to discard.
277+
/// </remarks>
278+
/// <param name="beforeSendScreenshot">The callback function to invoke before sending screenshots.</param>
279+
public void SetBeforeSendScreenshot(Func<Texture2D, SentryEvent, Texture2D?> beforeSendScreenshot)
267280
{
268-
_beforeCaptureScreenshot = beforeAttachScreenshot;
281+
BeforeSendScreenshotInternal = beforeSendScreenshot;
269282
}
270283

271284
private Func<bool>? _beforeCaptureViewHierarchy;

src/Sentry.Unity/SentryUnitySdk.cs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,34 @@ public void CaptureFeedback(string message, string? email, string? name, bool ad
121121
return;
122122
}
123123

124-
var hint = addScreenshot
125-
? SentryHint.WithAttachments(
126-
new SentryAttachment(
127-
AttachmentType.Default,
128-
new ByteAttachmentContent(SentryScreenshot.Capture(_options)),
129-
"screenshot.jpg",
130-
"image/jpeg"))
131-
: null;
124+
SentryHint? hint = null;
125+
if (addScreenshot)
126+
{
127+
Texture2D? screenshot = null;
128+
129+
try
130+
{
131+
screenshot = SentryScreenshot.CreateNewScreenshotTexture2D(_options);
132+
var screenshotBytes = screenshot.EncodeToJPG(_options.ScreenshotCompression);
133+
134+
if (screenshotBytes.Length > 0)
135+
{
136+
hint = SentryHint.WithAttachments(
137+
new SentryAttachment(
138+
AttachmentType.Default,
139+
new ByteAttachmentContent(screenshotBytes),
140+
"screenshot.jpg",
141+
"image/jpeg"));
142+
}
143+
}
144+
finally
145+
{
146+
if (screenshot)
147+
{
148+
UnityEngine.Object.Destroy(screenshot);
149+
}
150+
}
151+
}
132152

133153
Sentry.SentrySdk.CurrentHub.CaptureFeedback(message, email, name, hint: hint);
134154
}

0 commit comments

Comments
 (0)