Files
main/Tweaks/DontEatMyContent/Tweak.x
2023-06-27 09:54:41 +02:00

279 lines
10 KiB
Plaintext

#import <sys/utsname.h>
#import <rootless.h>
#import "Tweak.h"
#define UNSUPPORTED_DEVICES @[@"iPhone14,3", @"iPhone14,6", @"iPhone14,8"]
#define THRESHOLD 1.99
CGFloat constant; // Makes rendering view a bit larger since constraining to safe area leaves a gap between the notch/Dynamic Island and video
static CGFloat videoAspectRatio = 16/9;
static BOOL isZoomedToFill = NO;
static BOOL isEngagementPanelVisible = NO;
static BOOL isRemoveEngagementPanelViewControllerWithIdentifierCalled = NO;
static MLHAMSBDLSampleBufferRenderingView *renderingView;
static NSLayoutConstraint *widthConstraint, *heightConstraint, *centerXConstraint, *centerYConstraint;
static BOOL DEMC_isDeviceSupported();
static void DEMC_activateConstraints();
static void DEMC_deactivateConstraints();
static void DEMC_centerRenderingView();
void DEMC_showSnackBar(NSString *text);
NSBundle *DEMC_getTweakBundle();
%group DEMC_Tweak
// Retrieve video aspect ratio
%hook YTPlayerView
- (void)setAspectRatio:(CGFloat)aspectRatio {
%orig(aspectRatio);
videoAspectRatio = aspectRatio;
}
%end
%hook YTPlayerViewController
- (void)viewDidAppear:(BOOL)animated {
YTPlayerView *playerView = [self playerView];
UIView *renderingViewContainer = [playerView valueForKey:@"_renderingViewContainer"];
renderingView = [playerView renderingView];
widthConstraint = [renderingView.widthAnchor constraintEqualToAnchor:renderingViewContainer.safeAreaLayoutGuide.widthAnchor constant:constant];
heightConstraint = [renderingView.heightAnchor constraintEqualToAnchor:renderingViewContainer.safeAreaLayoutGuide.heightAnchor constant:constant];
centerXConstraint = [renderingView.centerXAnchor constraintEqualToAnchor:renderingViewContainer.centerXAnchor];
centerYConstraint = [renderingView.centerYAnchor constraintEqualToAnchor:renderingViewContainer.centerYAnchor];
if (IS_COLOR_VIEWS_ENABLED) {
playerView.backgroundColor = [UIColor blueColor];
renderingViewContainer.backgroundColor = [UIColor greenColor];
renderingView.backgroundColor = [UIColor redColor];
} else {
playerView.backgroundColor = nil;
renderingViewContainer.backgroundColor = nil;
renderingView.backgroundColor = [UIColor blackColor];
}
YTMainAppVideoPlayerOverlayViewController *activeVideoPlayerOverlay = [self activeVideoPlayerOverlay];
// Must check class since YTInlineMutedPlaybackPlayerOverlayViewController doesn't have -(BOOL)isFullscreen
if ([activeVideoPlayerOverlay isKindOfClass:%c(YTMainAppVideoPlayerOverlayViewController)] &&
[activeVideoPlayerOverlay isFullscreen] && !isZoomedToFill && !isEngagementPanelVisible)
DEMC_activateConstraints();
%orig(animated);
}
// New video played
- (void)playbackController:(id)playbackController didActivateVideo:(id)video withPlaybackData:(id)playbackData {
%orig(playbackController, video, playbackData);
isEngagementPanelVisible = NO;
isRemoveEngagementPanelViewControllerWithIdentifierCalled = NO;
if ([[self activeVideoPlayerOverlay] isFullscreen])
// New video played while in full screen (landscape)
// Activate since new videos played in full screen aren't zoomed-to-fill by default
// (i.e. the notch/Dynamic Island will cut into content when playing a new video in full screen)
DEMC_activateConstraints();
else if (![self isCurrentVideoVertical] && ((YTPlayerView *)[self playerView]).userInteractionEnabled)
DEMC_deactivateConstraints();
}
- (void)setPlayerViewLayout:(int)layout {
%orig(layout);
if (![[self activeVideoPlayerOverlay] isKindOfClass:%c(YTMainAppVideoPlayerOverlayViewController)]) return;
switch (layout) {
case 1: // Mini bar
break;
case 2:
DEMC_deactivateConstraints();
break;
case 3: // Fullscreen
if (!isZoomedToFill && !isEngagementPanelVisible) DEMC_activateConstraints();
break;
default:
break;
}
}
%end
// Pinch to zoom
%hook YTVideoFreeZoomOverlayView
- (void)didRecognizePinch:(UIPinchGestureRecognizer *)pinchGestureRecognizer {
DEMC_deactivateConstraints();
%orig(pinchGestureRecognizer);
}
// Detect zoom to fill
- (void)showLabelForSnapState:(NSInteger)snapState {
if (snapState == 0) { // Original
isZoomedToFill = NO;
DEMC_activateConstraints();
} else if (snapState == 1) { // Zoomed to fill
isZoomedToFill = YES;
// No need to deactivate constraints as it's already done in -(void)didRecognizePinch:(UIPinchGestureRecognizer *)
}
%orig(snapState);
}
%end
// Mini bar dismiss
%hook YTWatchMiniBarViewController
- (void)dismissMiniBarWithVelocity:(CGFloat)velocity gestureType:(int)gestureType {
%orig(velocity, gestureType);
isZoomedToFill = NO; // YouTube undoes zoom-to-fill when mini bar is dismissed
}
- (void)dismissMiniBarWithVelocity:(CGFloat)velocity gestureType:(int)gestureType skipShouldDismissCheck:(BOOL)skipShouldDismissCheck {
%orig(velocity, gestureType, skipShouldDismissCheck);
isZoomedToFill = NO;
}
%end
%hook YTMainAppEngagementPanelViewController
// Engagement panel (comment, description, etc.) about to show up
- (void)viewWillAppear:(BOOL)animated {
if ([self isPeekingSupported]) {
// Shorts (only Shorts support peeking, I think)
} else {
// Everything else
isEngagementPanelVisible = YES;
if ([self isLandscapeEngagementPanel]) {
DEMC_deactivateConstraints();
}
}
%orig(animated);
}
%end
%hook YTEngagementPanelContainerViewController
// Engagement panel about to dismiss
- (void)notifyEngagementPanelContainerControllerWillHideFinalPanel {
// Crashes if plays new video while in full screen causing engagement panel dismissal
// Must check if engagement panel was dismissed because new video played
// (i.e. if -(void)removeEngagementPanelViewControllerWithIdentifier:(id) was called prior)
if (![self isPeekingSupported] && !isRemoveEngagementPanelViewControllerWithIdentifierCalled) {
isEngagementPanelVisible = NO;
if ([self isLandscapeEngagementPanel] && !isZoomedToFill) {
DEMC_activateConstraints();
}
}
%orig;
}
- (void)removeEngagementPanelViewControllerWithIdentifier:(id)identifier {
// Usually called when engagement panel is open & new video is played or mini bar is dismissed
isRemoveEngagementPanelViewControllerWithIdentifierCalled = YES;
%orig(identifier);
}
%end
%end // group DEMC_Tweak
%group DEMC_UnsupportedDevice
// Get tweak settings' index path & prevent it from being opened on unsupported devices
id settingsCollectionView;
NSIndexPath *tweakIndexPath;
%hook YTCollectionViewController
- (id)collectionView:(id)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
YTSettingsCell *cell = %orig;
if ([cell isKindOfClass:%c(YTSettingsCell)]) {
YTLabel *title = [cell valueForKey:@"_titleLabel"];
if ([title.text isEqualToString:DEMC]) {
settingsCollectionView = collectionView;
tweakIndexPath = indexPath;
}
}
return cell;
}
- (BOOL)collectionView:(id)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
if (collectionView == settingsCollectionView && indexPath == tweakIndexPath) {
NSBundle *bundle = DEMC_getTweakBundle();
DEMC_showSnackBar(LOCALIZED_STRING(@"UNSUPPORTED_DEVICE"));
return NO;
}
return %orig;
}
%end
%end // group DEMC_UnsupportedDevice
%ctor {
if (!DEMC_isDeviceSupported()) {
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:ENABLED_KEY];
%init(DEMC_UnsupportedDevice);
return;
}
constant = [[NSUserDefaults standardUserDefaults] floatForKey:SAFE_AREA_CONSTANT_KEY];
if (constant == 0) { // First launch probably
constant = DEFAULT_CONSTANT;
[[NSUserDefaults standardUserDefaults] setFloat:constant forKey:SAFE_AREA_CONSTANT_KEY];
}
if (IS_TWEAK_ENABLED) %init(DEMC_Tweak);
}
static BOOL DEMC_isDeviceSupported() {
// Get device model identifier (e.g. iPhone14,4)
// https://stackoverflow.com/a/11197770/19227228
struct utsname systemInfo;
uname(&systemInfo);
NSString *deviceModelId = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
NSArray *unsupportedModelIds = UNSUPPORTED_DEVICES;
for (NSString *identifier in unsupportedModelIds) {
if ([deviceModelId isEqualToString:identifier]) {
return NO;
}
}
if ([deviceModelId containsString:@"iPhone"]) {
if ([deviceModelId isEqualToString:@"iPhone13,1"]) {
// iPhone 12 mini
return YES;
}
NSString *modelNumber = [[deviceModelId stringByReplacingOccurrencesOfString:@"iPhone" withString:@""] stringByReplacingOccurrencesOfString:@"," withString:@"."];
if ([modelNumber floatValue] >= 14.0) {
// iPhone 13 series and newer
return YES;
} else return NO;
} else return NO;
}
static void DEMC_activateConstraints() {
if (videoAspectRatio < THRESHOLD) {
DEMC_deactivateConstraints();
return;
}
// NSLog(@"activate");
DEMC_centerRenderingView();
renderingView.translatesAutoresizingMaskIntoConstraints = NO;
widthConstraint.active = YES;
heightConstraint.active = YES;
}
static void DEMC_deactivateConstraints() {
// NSLog(@"deactivate");
renderingView.translatesAutoresizingMaskIntoConstraints = YES;
}
static void DEMC_centerRenderingView() {
centerXConstraint.active = YES;
centerYConstraint.active = YES;
}
void DEMC_showSnackBar(NSString *text) {
YTHUDMessage *message = [%c(YTHUDMessage) messageWithText:text];
GOOHUDManagerInternal *manager = [%c(GOOHUDManagerInternal) sharedInstance];
[manager showMessageMainThread:message];
}
NSBundle *DEMC_getTweakBundle() {
static NSBundle *bundle = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"DontEatMyContent" ofType:@"bundle"];
if (bundlePath)
bundle = [NSBundle bundleWithPath:bundlePath];
else // Rootless
bundle = [NSBundle bundleWithPath:ROOT_PATH_NS(@"/Library/Application Support/DontEatMyContent.bundle")];
});
return bundle;
}