Fullscreen apps above the MacBook notch — Alin Panaitiu

On MacBook laptops with a notch, the realm above the notch is reserved for the menu bar for many purposes. That’s good for regular window utilization, because the menubar now not takes valuable vertical house that may very well be used for the content material.
Going fullscreen although, solely makes that space black (in order to cover the notch) however apps will nonetheless render beneath the notch, leaving about 100k pixels ineffective.
So listed here are some directions on how one can fullscreen a window above the notch in probably the most hackish approach potential.
By the way in which, for those who want that previous MacBook and not using a notch design, my Lunar app can disable the notch by making use of a hidden decision.
# Outdated-style fullscreen
Each macOS window is definitely a NSWindow
, and that class has a toggleFullScreen()
technique.
However what that does is transfer the window to its personal particular Mission Management House and render it fullscreen beneath the notch. Not what I need.
There is a factor known as old-style fullscreen which some apps assist natively (Chic Textual content, kitty, IINA and so on.).
As an alternative of calling toggleFullScreen()
, this technique does the next:
- makes the window borderless
- units up the app to auto-hide the menubar and dock
- units the window body equal to the display body
This is how IINA does it for instance: iina/MainWindowController.swift · iina/iina · GitHub
The issue is that, whereas apps can change their very own home windows as they please, there is no approach for an app to alter these attributes on home windows of different apps.
# Injecting code
Utilizing Frida we will inject and run code contained in the context of any operating course of.
We first have to disable SIP although, as code injection is restricted by default for apparent safety causes.
Under we outline our Frida script, which we will save in ~/Paperwork/OverNotch.js for later utilization.
OverNotch.js
var NSApplicationPresentationAutoHideMenuBar = 1 << 2
var NSApplicationPresentationAutoHideDock = 1 << 0
var NSWindowStyleMaskBorderless = 0
var NSApplicationPresentationDefault = 0
world.app = ObjC.courses.NSApplication.sharedApplication()
world.arrayFromNSArray = (nsArray) => {
var jsArray = []
var rely = nsArray.rely()
for (var i = 0; i < rely; i++) {
jsArray[i] = nsArray.objectAtIndex_(i)
}
return jsArray
}
world.mainWindow = () => {
if (world.app.mainWindow()) { return world.app.mainWindow() }
if (world.app.keyWindow()) { return world.app.keyWindow() }
var home windows = arrayFromNSArray(world.app.home windows())
var index = Math.min(...home windows.map((w) => w.orderedIndex()))
return home windows.discover((w) => w.orderedIndex() == index)
}
world.w = mainWindow()
world.toggleFullScreen = (windowNum) => {
if (!windowNum) { windowNum = world.w.windowNumber() }
var dict = ObjC.courses.NSThread.mainThread().threadDictionary()
if (dict.valueForKey_(windowNum.toString())) {
stopFullScreen(windowNum)
} else {
makeFullScreen(windowNum)
}
}
world.stopFullScreen = (windowNum) => {
Interceptor.revert(app.setPresentationOptions_.implementation)
ObjC.schedule(ObjC.mainQueue, () => {
world.app.setPresentationOptions_(NSApplicationPresentationDefault)
})
var window = windowNum ? world.app.windowWithWindowNumber_(windowNum) : world.w
if (!window) {
return console.log('Window not discovered')
}
ObjC.schedule(ObjC.mainQueue, () => {
var key = window.windowNumber().toString()
var dict = ObjC.courses.NSThread.mainThread().threadDictionary()
if (dict.valueForKey_(key)) {
window.setStyleMask_(dict.valueForKey_(key).unsignedLongLongValue())
dict.removeObjectForKey_(key)
}
window.setFrameUsingName_(key)
window.setIsMovable_(true)
Interceptor.revert(app.setPresentationOptions_.implementation)
})
}
world.makeFullScreen = (windowNum) => {
var window = windowNum ? world.app.windowWithWindowNumber_(windowNum) : world.w
if (!window) {
return console.log('Window not discovered')
}
ObjC.schedule(ObjC.mainQueue, () => {
var key = window.windowNumber().toString()
var dict = ObjC.courses.NSThread.mainThread().threadDictionary()
var worth = ObjC.courses.NSNumber.numberWithUnsignedLongLong_(window.styleMask())
dict.setValue_forKey_(worth, key)
window.saveFrameUsingName_(key)
world.app.setPresentationOptions_(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)
window.setStyleMask_(NSWindowStyleMaskBorderless)
window.setFrame_display_(window.display().body(), true)
window.setIsMovable_(false)
Interceptor.substitute(app.setPresentationOptions_.implementation, new NativeCallback((masks) => {}))
})
}
Then as a check, have a Safari window open, and run the next to make it fullscreen:
sudo frida -q -l ~/Paperwork/OverNotch.js -e "toggleFullScreen()" Safari
Word that this does not work with all home windows, and a few apps will crash when their window styleMask
is modified.
# Hotkey
I want to bind fn - a
to fullscreen the present window utilizing skhd.
I’d normally bind
rcmd - a
as it’s kind of simpler to press, however since I surrendered the⌘ Proper Command
key to my rcmd app switcher, I am utilizingfn
for these system-wide hotkeys.
This is what I added in ~/.skhdrc
to try this:
fn - a : sudo frida -q -l ~/Paperwork/OverNotch.js -e "toggleFullScreen()" $(osascript -e 'inform software "System Occasions" to get unix id of first software course of whose frontmost is true')
# Why would you want such a factor?
I personally wished it for having Home windows 11 look as natively as potential by Parallels Desktop. I nonetheless want it sometimes for {hardware} that solely gives a Home windows configuration program.
You may as well use it to get an immersive view of particular webpages. This is NightDrive for instance:
Or perhaps have a extra centered zen-mode on observe taking apps. This is NotePlan in my case:
# SIP disabled? How about no?
Frida and different debuggers like lldb and gdb use the task_for_pid
API for attaching to a operating course of.
That does not essentially want SIP being disabled. For task_for_pid
to succeed on a course of, that course of wants to fulfill one of many following necessities:
- be unsigned
- be signed with out Hardened Runtime
- embrace the
com.apple.safety.get-task-allow
entitlement
Fortuitously, we will alter the signature of any non-system app with out disabling SIP.
Sadly that is not very easy as a result of we have to make sure that we signal all the extra bundles contained in the app, and we have to handle not eradicating present entitlements.
As a result of I am utilizing fish shell, this is how I method this process:
operate resign-bundle -a bundle
# Dump present entitlements as a PLIST
codesign -d --entitlements - --xml "$bundle" >/tmp/entitlements.xml
if not check -s /tmp/entitlements.xml
# Signal with out Hardened Runtime
codesign -fs $CODESIGN_CERT "$bundle"
return
finish
# Add `get-task-allow` to the entitlements to permit debuggers
/usr/libexec/PlistBuddy -c "Add :com.apple.safety.get-task-allow bool true" /tmp/entitlements.xml
# Signal with Hardened Runtime and `get-task-allow`
codesign -fs $CODESIGN_CERT -o runtime --timestamp --entitlements /tmp/entitlements.xml "$bundle"
finish
operate resign-app -a app
# Backup present app
mkdir -p ~/.cache/resign-app-backups/
if not check -d "~/.cache/resign-app-backups/$(basename "$app")"
rsync -avz "$app" ~/.cache/resign-app-backups/
finish
# Resign bundles contained in the app
fd -uu '.(app|framework|dylib|xpc|appex)$' "$app" -j 1 -x fish -c 'resign-bundle {}'
# Resign the app itself
resign-bundle "$app"
finish
Testing this on RealVNC Viewer:
> resign-app "/Purposes/VNC Viewer.app"
Executable=/Purposes/VNC Viewer.app/Contents/MacOS/vncviewer
/Purposes/VNC Viewer.app: changing present signature
exhibiting how native fullscreen differs from legacy fullscreen
I additionally tried testing this on VirtualBuddy however as a result of it has a required entitlement (com.apple.vm.networking
) and an embedded provisioning profile tied to the developer certificates, this received too sophisticated to warrant the hassle.
System apps can also’t work with this method as a result of a few of them dwell in immutable volumes known as Cryptexes. For instance this is the place Safari could be discovered on macOS Ventura:
# Safari lives at /System/Volumes/Preboot/Cryptexes/App/System/Purposes/Safari.app
> codesign --remove-signature '/System/Volumes/Preboot/Cryptexes/App/System/Purposes/Safari.app'
/System/Volumes/Preboot/Cryptexes/App/System/Purposes/Safari.app: inner error in Code Signing subsystem