|
#import <Cocoa/Cocoa.h> |
|
#import <CoreGraphics/CoreGraphics.h> |
|
#import <signal.h> |
|
|
|
@interface NoNotchApp : NSObject <NSApplicationDelegate> |
|
@property (strong) NSStatusItem *statusItem; |
|
@property (strong) NSWindow *overlayWindow; |
|
@property CGFloat notchHeight; |
|
@property CGDisplayModeRef originalMode; |
|
@end |
|
|
|
void signalHandler(int sig); |
|
|
|
@implementation NoNotchApp |
|
|
|
- (void)applicationDidFinishLaunching:(NSNotification *)notification { |
|
[self setupStatusItem]; |
|
[self changeResolution]; |
|
[self createNotchOverlay]; |
|
[self setupSignalHandlers]; |
|
} |
|
|
|
- (void)setupSignalHandlers { |
|
signal(SIGINT, signalHandler); |
|
signal(SIGTERM, signalHandler); |
|
} |
|
|
|
void signalHandler(int sig) { |
|
NSLog(@"Received signal %d, restoring resolution...", sig); |
|
// Get the app delegate and restore resolution |
|
NoNotchApp *delegate = (NoNotchApp *)[[NSApplication sharedApplication] delegate]; |
|
[delegate restoreResolution]; |
|
exit(0); |
|
} |
|
|
|
- (void)setupStatusItem { |
|
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; |
|
self.statusItem.button.title = @"NoNotch"; |
|
|
|
NSMenu *menu = [[NSMenu alloc] init]; |
|
[menu addItemWithTitle:@"Restore & Quit" action:@selector(quit:) keyEquivalent:@"q"]; |
|
self.statusItem.menu = menu; |
|
} |
|
|
|
- (void)changeResolution { |
|
CGDirectDisplayID displayID = kCGDirectMainDisplay; |
|
NSScreen *screen = [NSScreen mainScreen]; |
|
if (!screen) { |
|
NSLog(@"No main screen found"); |
|
return; |
|
} |
|
|
|
self.notchHeight = screen.safeAreaInsets.top; |
|
NSLog(@"Notch height: %.2f", self.notchHeight); |
|
|
|
// Store original mode |
|
self.originalMode = CGDisplayCopyDisplayMode(displayID); |
|
if (!self.originalMode) { |
|
NSLog(@"Could not get original display mode"); |
|
return; |
|
} |
|
|
|
size_t originalWidth = CGDisplayModeGetWidth(self.originalMode); |
|
size_t originalHeight = CGDisplayModeGetHeight(self.originalMode); |
|
NSLog(@"Original resolution: %zux%zu", originalWidth, originalHeight); |
|
|
|
// Calculate new height (subtract notch height) |
|
size_t newHeight = originalHeight - (size_t)self.notchHeight; |
|
NSLog(@"Target height: %zu", newHeight); |
|
|
|
// Create dictionary to include HiDPI modes |
|
CFDictionaryRef options = CFDictionaryCreate(NULL, |
|
(const void**)&kCGDisplayShowDuplicateLowResolutionModes, |
|
(const void**)&kCFBooleanTrue, 1, |
|
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
|
|
|
// Get all available display modes including HiDPI |
|
CFArrayRef modes = CGDisplayCopyAllDisplayModes(displayID, options); |
|
CFRelease(options); |
|
|
|
if (!modes) { |
|
NSLog(@"Could not get display modes"); |
|
return; |
|
} |
|
|
|
CGDisplayModeRef targetMode = NULL; |
|
CFIndex modeCount = CFArrayGetCount(modes); |
|
NSLog(@"Found %ld display modes (including HiDPI)", modeCount); |
|
|
|
// Find a mode with same width but reduced height |
|
for (CFIndex i = 0; i < modeCount; i++) { |
|
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(modes, i); |
|
size_t modeWidth = CGDisplayModeGetWidth(mode); |
|
size_t modeHeight = CGDisplayModeGetHeight(mode); |
|
|
|
NSLog(@"Mode %ld: %zux%zu", i, modeWidth, modeHeight); |
|
|
|
// Look for exact match or closest smaller height with same width |
|
if (modeWidth == originalWidth && modeHeight == newHeight) { |
|
NSLog(@"Found exact target mode: %zux%zu", modeWidth, modeHeight); |
|
targetMode = mode; |
|
break; |
|
} else if (modeWidth == originalWidth && modeHeight < originalHeight && modeHeight >= newHeight - 50) { |
|
NSLog(@"Found close target mode: %zux%zu", modeWidth, modeHeight); |
|
targetMode = mode; |
|
} |
|
} |
|
|
|
// Apply the new resolution |
|
if (targetMode) { |
|
NSLog(@"Applying new resolution..."); |
|
CGDisplayConfigRef config; |
|
CGError error = CGBeginDisplayConfiguration(&config); |
|
if (error != kCGErrorSuccess) { |
|
NSLog(@"Failed to begin display configuration: %d", error); |
|
CFRelease(modes); |
|
return; |
|
} |
|
|
|
error = CGConfigureDisplayWithDisplayMode(config, displayID, targetMode, NULL); |
|
if (error != kCGErrorSuccess) { |
|
NSLog(@"Failed to configure display mode: %d", error); |
|
CGCancelDisplayConfiguration(config); |
|
CFRelease(modes); |
|
return; |
|
} |
|
|
|
error = CGCompleteDisplayConfiguration(config, kCGConfigureForSession); |
|
if (error != kCGErrorSuccess) { |
|
NSLog(@"Failed to complete display configuration: %d", error); |
|
} else { |
|
NSLog(@"Successfully changed resolution"); |
|
} |
|
} else { |
|
NSLog(@"No suitable display mode found"); |
|
} |
|
|
|
CFRelease(modes); |
|
} |
|
|
|
- (void)createNotchOverlay { |
|
// Create overlay at the top of the physical screen |
|
NSScreen *screen = [NSScreen mainScreen]; |
|
if (!screen) return; |
|
|
|
// The overlay should cover the area above the new resolution |
|
NSRect overlayFrame = NSMakeRect(0, |
|
screen.frame.size.height, |
|
screen.frame.size.width, |
|
self.notchHeight); |
|
|
|
self.overlayWindow = [[NSWindow alloc] initWithContentRect:overlayFrame |
|
styleMask:NSWindowStyleMaskBorderless |
|
backing:NSBackingStoreBuffered |
|
defer:NO]; |
|
|
|
self.overlayWindow.backgroundColor = [NSColor blackColor]; |
|
self.overlayWindow.level = kCGMaximumWindowLevel; |
|
self.overlayWindow.ignoresMouseEvents = NO; |
|
[self.overlayWindow makeKeyAndOrderFront:nil]; |
|
} |
|
|
|
- (void)restoreResolution { |
|
if (self.originalMode) { |
|
CGDirectDisplayID displayID = kCGDirectMainDisplay; |
|
CGDisplayConfigRef config; |
|
CGBeginDisplayConfiguration(&config); |
|
CGConfigureDisplayWithDisplayMode(config, displayID, self.originalMode, NULL); |
|
CGCompleteDisplayConfiguration(config, kCGConfigureForSession); |
|
CGDisplayModeRelease(self.originalMode); |
|
} |
|
} |
|
|
|
- (void)quit:(id)sender { |
|
[self restoreResolution]; |
|
[NSApplication.sharedApplication terminate:nil]; |
|
} |
|
|
|
@end |
|
|
|
int main(int argc, const char * argv[]) { |
|
@autoreleasepool { |
|
NSApplication *app = [NSApplication sharedApplication]; |
|
NoNotchApp *delegate = [[NoNotchApp alloc] init]; |
|
app.delegate = delegate; |
|
[app setActivationPolicy:NSApplicationActivationPolicyAccessory]; |
|
[app run]; |
|
} |
|
return 0; |
|
} |