-
-
Notifications
You must be signed in to change notification settings - Fork 51
Add screenshot capturing for Mac/iOS #849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
ebd5db0
7b685c9
36026ed
dcf22ab
0d9deaf
86df572
0d12285
40eb0bf
a6bf79c
ba2cb4e
5c2694d
1716702
4f21912
3d16918
0d2f4e9
7cb90cd
705f551
b26a7d8
50c8e74
6d1a1c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,10 @@ | |
|
||
#include "GenericPlatform/GenericPlatformOutputDevices.h" | ||
#include "HAL/FileManager.h" | ||
#include "HAL/PlatformSentryAttachment.h" | ||
#include "Misc/CoreDelegates.h" | ||
#include "Misc/FileHelper.h" | ||
#include "Misc/Paths.h" | ||
#include "UObject/GarbageCollection.h" | ||
#include "UObject/UObjectThreadContext.h" | ||
#include "Utils/SentryLogUtils.h" | ||
|
@@ -57,6 +61,7 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, US | |
options.sampleRate = [NSNumber numberWithFloat:settings->SampleRate]; | ||
options.maxBreadcrumbs = settings->MaxBreadcrumbs; | ||
options.sendDefaultPii = settings->SendDefaultPii; | ||
options.maxAttachmentSize = settings->MaxAttachmentSize; | ||
#if SENTRY_UIKIT_AVAILABLE | ||
options.attachScreenshot = settings->AttachScreenshot; | ||
#endif | ||
|
@@ -68,6 +73,12 @@ void FAppleSentrySubsystem::InitWithSettings(const USentrySettings* settings, US | |
} | ||
return scope; | ||
}; | ||
options.onCrashedLastRun = ^(SentryEvent* event) { | ||
if (settings->AttachScreenshot) | ||
{ | ||
UploadScreenshotForEvent(MakeShareable(new SentryIdApple(event.eventId))); | ||
} | ||
}; | ||
options.beforeSend = ^SentryEvent* (SentryEvent* event) { | ||
if (FUObjectThreadContext::Get().IsRoutingPostLoad) | ||
{ | ||
|
@@ -369,3 +380,51 @@ TSharedPtr<ISentryTransactionContext> FAppleSentrySubsystem::ContinueTrace(const | |
|
||
return MakeShareable(new SentryTransactionContextApple(transactionContext)); | ||
} | ||
|
||
void FAppleSentrySubsystem::UploadScreenshotForEvent(TSharedPtr<ISentryId> eventId) const | ||
{ | ||
const FString& screenshotFilePath = GetScreenshotPath(); | ||
|
||
IFileManager& fileManager = IFileManager::Get(); | ||
if (!fileManager.FileExists(*screenshotFilePath)) | ||
{ | ||
UE_LOG(LogSentrySdk, Error, TEXT("Failed to upload screenshot.")); | ||
return; | ||
} | ||
|
||
const FString& screenshotFilePathExt = fileManager.ConvertToAbsolutePathForExternalAppForRead(*screenshotFilePath); | ||
|
||
SentryAttachment* screenshotAttachment = [[SENTRY_APPLE_CLASS(SentryAttachment) alloc] initWithPath:screenshotFilePathExt.GetNSString()]; | ||
|
||
SentryOptions* options = [SENTRY_APPLE_CLASS(PrivateSentrySDKOnly) options]; | ||
int32 size = options.maxAttachmentSize; | ||
|
||
SentryEnvelopeItem* envelopeItem = [[SENTRY_APPLE_CLASS(SentryEnvelopeItem) alloc] initWithAttachment:screenshotAttachment maxAttachmentSize:size]; | ||
|
||
SentryId* id = StaticCastSharedPtr<SentryIdApple>(eventId)->GetNativeObject(); | ||
|
||
SentryEnvelope* envelope = [[SENTRY_APPLE_CLASS(SentryEnvelope) alloc] initWithId:id singleItem:envelopeItem]; | ||
|
||
[SENTRY_APPLE_CLASS(PrivateSentrySDKOnly) captureEnvelope:envelope]; | ||
|
||
// After uploading screenshot create its timestamped backup | ||
CreateScreenshotBackup(); | ||
tustanivsky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} | ||
|
||
void FAppleSentrySubsystem::CreateScreenshotBackup() const | ||
{ | ||
const FString& screenshotFilePath = GetScreenshotPath(); | ||
|
||
IFileManager& fileManager = IFileManager::Get(); | ||
if (fileManager.FileExists(*screenshotFilePath)) | ||
{ | ||
FString name, extension; | ||
FString(screenshotFilePath).Split(TEXT("."), &name, &extension, ESearchCase::CaseSensitive, ESearchDir::FromEnd); | ||
FDateTime originalTime = fileManager.GetTimeStamp(*screenshotFilePath); | ||
FString backupFilePath = FString::Printf(TEXT("%s%s%s.%s"), *name, TEXT("-backup-"), *originalTime.ToString(), *extension); | ||
if (!fileManager.Move(*backupFilePath, *screenshotFilePath, true)) | ||
{ | ||
UE_LOG(LogSentrySdk, Error, TEXT("Failed to backup screenshot.")); | ||
|
||
} | ||
} | ||
tustanivsky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
#include "IOS/IOSSentrySubsystem.h" | ||
|
||
#include "IOS/IOSAppDelegate.h" | ||
|
||
#include "SentryDefines.h" | ||
#include "SentrySettings.h" | ||
|
||
#include "Misc/CoreDelegates.h" | ||
#include "Misc/FileHelper.h" | ||
#include "Misc/Paths.h" | ||
|
||
void FIOSSentrySubsystem::InitWithSettings(const USentrySettings* Settings, USentryBeforeSendHandler* BeforeSendHandler, USentryBeforeBreadcrumbHandler* BeforeBreadcrumbHandler, | ||
USentryTraceSampler* TraceSampler) | ||
{ | ||
FAppleSentrySubsystem::InitWithSettings(Settings, BeforeSendHandler, BeforeBreadcrumbHandler, TraceSampler); | ||
} | ||
|
||
void FIOSSentrySubsystem::TryCaptureScreenshot() const | ||
{ | ||
dispatch_sync(dispatch_get_main_queue(), ^{ | ||
UIGraphicsBeginImageContextWithOptions([IOSAppDelegate GetDelegate].RootView.bounds.size, NO, 2.0f); | ||
[[IOSAppDelegate GetDelegate].RootView drawViewHierarchyInRect:[IOSAppDelegate GetDelegate].RootView.bounds afterScreenUpdates:YES]; | ||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); | ||
UIGraphicsEndImageContext(); | ||
|
||
NSData *ImageData = UIImagePNGRepresentation(image); | ||
|
||
TArray<uint8> ImageBytes; | ||
uint32 SavedSize = ImageData.length; | ||
ImageBytes.AddUninitialized(SavedSize); | ||
FPlatformMemory::Memcpy(ImageBytes.GetData(), [ImageData bytes], SavedSize); | ||
|
||
FString FilePath = GetScreenshotPath(); | ||
|
||
if (FFileHelper::SaveArrayToFile(ImageBytes, *FilePath)) | ||
{ | ||
UE_LOG(LogSentrySdk, Log, TEXT("Screenshot saved to: %s"), *FilePath); | ||
} | ||
else | ||
{ | ||
UE_LOG(LogSentrySdk, Error, TEXT("Failed to save screenshot.")); | ||
} | ||
}); | ||
} | ||
|
||
FString FIOSSentrySubsystem::GetScreenshotPath() const | ||
{ | ||
return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("screenshot.png")); | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
#include "Mac/MacSentrySubsystem.h" | ||
|
||
#include "SentryIdApple.h" | ||
|
||
#include "SentryDefines.h" | ||
#include "SentryModule.h" | ||
#include "SentrySettings.h" | ||
|
||
#include "Misc/CoreDelegates.h" | ||
#include "Misc/FileHelper.h" | ||
#include "Misc/Paths.h" | ||
|
||
void FMacSentrySubsystem::InitWithSettings(const USentrySettings* Settings, USentryBeforeSendHandler* BeforeSendHandler, USentryBeforeBreadcrumbHandler* BeforeBreadcrumbHandler, | ||
USentryTraceSampler* TraceSampler) | ||
{ | ||
FAppleSentrySubsystem::InitWithSettings(Settings, BeforeSendHandler, BeforeBreadcrumbHandler, TraceSampler); | ||
|
||
isScreenshotAttachmentEnabled = Settings->AttachScreenshot; | ||
|
||
if (Settings->AttachScreenshot) | ||
{ | ||
FCoreDelegates::OnHandleSystemError.AddLambda([this]() | ||
{ | ||
TryCaptureScreenshot(); | ||
}); | ||
} | ||
} | ||
|
||
TSharedPtr<ISentryId> FMacSentrySubsystem::CaptureEnsure(const FString& type, const FString& message) | ||
{ | ||
TSharedPtr<ISentryId> id = FAppleSentrySubsystem::CaptureEnsure(type, message); | ||
|
||
if (isScreenshotAttachmentEnabled) | ||
{ | ||
TryCaptureScreenshot(); | ||
|
||
UploadScreenshotForEvent(id); | ||
} | ||
|
||
return id; | ||
} | ||
|
||
void FMacSentrySubsystem::TryCaptureScreenshot() const | ||
{ | ||
NSWindow* MainWindow = [NSApp mainWindow]; | ||
if (!MainWindow) | ||
{ | ||
UE_LOG(LogSentrySdk, Log, TEXT("No main window found!")); | ||
return; | ||
} | ||
|
||
NSRect WindowRect = [MainWindow frame]; | ||
CGWindowID WindowID = (CGWindowID)[MainWindow windowNumber]; | ||
CGImageRef ScreenshotRef = CGWindowListCreateImage(WindowRect, kCGWindowListOptionIncludingWindow, WindowID, kCGWindowImageDefault); | ||
|
||
if (!ScreenshotRef) | ||
{ | ||
UE_LOG(LogSentrySdk, Log, TEXT("Failed to capture screenshot.")); | ||
tustanivsky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
return; | ||
} | ||
|
||
NSBitmapImageRep* BitmapRep = [[NSBitmapImageRep alloc] initWithCGImage:ScreenshotRef]; | ||
NSData* ImageData = [BitmapRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; | ||
|
||
TArray<uint8> ImageBytes; | ||
uint32 SavedSize = (uint32)[ImageData length]; | ||
ImageBytes.AddUninitialized(SavedSize); | ||
FPlatformMemory::Memcpy(ImageBytes.GetData(), [ImageData bytes], SavedSize); | ||
|
||
FString FilePath = GetScreenshotPath(); | ||
|
||
if (FFileHelper::SaveArrayToFile(ImageBytes, *FilePath)) | ||
{ | ||
UE_LOG(LogSentrySdk, Log, TEXT("Screenshot saved to: %s"), *FilePath); | ||
} | ||
else | ||
{ | ||
UE_LOG(LogSentrySdk, Log, TEXT("Failed to save screenshot.")); | ||
} | ||
|
||
CGImageRelease(ScreenshotRef); | ||
} | ||
|
||
FString FMacSentrySubsystem::GetScreenshotPath() const | ||
{ | ||
return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("screenshot.png")); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.