Last active
June 15, 2026 21:16
-
-
Save sp1ke23/fec555e0b83b6d48722227e27825fc00 to your computer and use it in GitHub Desktop.
Minimal MainMenu.xib file - HOWTO create a minimal macOS app without Xcode
Author
Author
HOWTO compile above xib into binary plist
$ ibtool --errors --warnings --notices --output-format human-readable-text --compile MainMenu.xib.out MainMenu.xib
$ file MainMenu.xib.out
MainMenu.xib.out: Apple binary property list
Author
main.m
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
assert(NSThread.isMainThread);
return NSApplicationMain(argc, argv);
}
AppDelegate.h
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end
AppDelegate.m
#import "AppDelegate.h"
@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
NSLog(@"> %s %@", __func__, notification);
NSAppleEventManager *appleEventManager = NSAppleEventManager.sharedAppleEventManager;
[appleEventManager
setEventHandler:self
andSelector:@selector(handleQuitEvent:withReplyEvent:)
forEventClass:kCoreEventClass
andEventID:kAEQuitApplication];
}
- (void)handleQuitEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
NSLog(@"> %s %@ %@", __func__, event, replyEvent);
[NSApp terminate:self];
}
- (void)applicationWillTerminate:(NSNotification *)notification {
NSLog(@"> %s %@", __func__, notification);
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
NSLog(@"> %s %@ %d", __func__, sender, flag);
return NO;
}
- (void)alertMenuItemClicked {
NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = @"WARNING";
alert.informativeText = @"You just clicked an alert menu item in Dock.";
alert.alertStyle = NSAlertStyleCritical;
[alert runModal];
}
- (nullable NSMenu *)applicationDockMenu:(NSApplication *)sender {
NSLog(@"> %s %@", __func__, sender);
NSMenu *menu = [[NSMenu alloc] init];
(void) [menu addItemWithTitle:@"Alert" action:@selector(alertMenuItemClicked) keyEquivalent:@""];
return menu;
}
@end
Author
Info.plist for foobar.app
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>19E224g</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>foobar</string>
<key>CFBundleIdentifier</key>
<string>com.example.app.foobar</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>foobar</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>11C29</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>19B90</string>
<key>DTSDKName</key>
<string>macosx10.15</string>
<key>DTXcode</key>
<string>1130</string>
<key>DTXcodeBuild</key>
<string>11C29</string>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 John Doe. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticTermination</key>
<true/>
</dict>
</plist>
The NSSupportsAutomaticTermination key is used to auto hide dock pin state when last active window of your app is closed, note that the backing app still running.
Author
HOWTO compile a minimal macOS app foobar.app
$ mkdir -p foobar.app/Contents/MacOS
$ mkdir -p foobar.app/Contents/Resources/Base.lproj
$ clang -x objective-c -Wall -Wextra main.m AppDelegate.m -framework Cocoa -fobjc-arc -fobjc-weak -o foobar.app/Contents/MacOS/foobar
$ ibtool --errors --warnings --notices --output-format human-readable-text --compile foobar.app/Contents/Resources/Base.lproj/MainMenu.nib MainMenu.xib
$ cp Info.plist foobar.app/Contents
$ printf %s 'APPL????' > foobar.app/Contents/PkgInfo
$ tree foobar.app
foobar.app
└── Contents
├── Info.plist
├── MacOS
│ └── foobar
├── PkgInfo
└── Resources
└── Base.lproj
└── MainMenu.nib
4 directories, 4 filesSample foobar.app run output
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.4
BuildVersion: 19E224g
$ foobar.app/Contents/MacOS/foobar
2020-09-01 15:35:53.693 foobar[46377:1181130] > -[AppDelegate applicationDidFinishLaunching:] NSConcreteNotification 0x7fc4c6e39af0 {name = NSApplicationDidFinishLaunchingNotification; object = <NSApplication: 0x7fc4c800a450>; userInfo = {
NSApplicationLaunchIsDefaultLaunchKey = 1;
}}
2020-09-01 15:35:56.139 foobar[46377:1181130] > -[AppDelegate applicationShouldHandleReopen:hasVisibleWindows:] <NSApplication: 0x7fc4c800a450> 0
2020-09-01 15:35:57.604 foobar[46377:1181130] > -[AppDelegate applicationDockMenu:] <NSApplication: 0x7fc4c800a450>
2020-09-01 15:36:00.681 foobar[46377:1181130] > -[AppDelegate applicationShouldHandleReopen:hasVisibleWindows:] <NSApplication: 0x7fc4c800a450> 0
2020-09-01 15:36:01.098 foobar[46377:1181130] > -[AppDelegate applicationDockMenu:] <NSApplication: 0x7fc4c800a450>
2020-09-01 15:36:02.463 foobar[46377:1181130] > -[AppDelegate handleQuitEvent:withReplyEvent:] <NSAppleEventDescriptor: 'aevt'\'quit'{ }> <NSAppleEventDescriptor: null()>
Author
[1] 46578 killed foobar.app/Contents/MacOS/foobar
$ lldb foobar.app/Contents/MacOS/foobar
(lldb) target create "foobar.app/Contents/MacOS/foobar"
Current executable set to 'foobar.app/Contents/MacOS/foobar' (x86_64).
(lldb) run
Process 46578 launched: '/private/tmp/t/foobar.app/Contents/MacOS/foobar' (x86_64)
2020-09-01 15:41:00.898274+0800 foobar[46578:1184250] > -[AppDelegate applicationDidFinishLaunching:] NSConcreteNotification 0x100429900 {name = NSApplicationDidFinishLaunchingNotification; object = <NSApplication: 0x1004148c0>; userInfo = {
NSApplicationLaunchIsDefaultLaunchKey = 1;
}}
2020-09-01 15:41:02.667148+0800 foobar[46578:1184250] > -[AppDelegate applicationShouldHandleReopen:hasVisibleWindows:] <NSApplication: 0x1004148c0> 0
2020-09-01 15:41:03.123983+0800 foobar[46578:1184250] > -[AppDelegate applicationDockMenu:] <NSApplication: 0x1004148c0>
2020-09-01 15:41:04.069800+0800 foobar[46578:1184250] > -[AppDelegate applicationShouldHandleReopen:hasVisibleWindows:] <NSApplication: 0x1004148c0> 0
2020-09-01 15:41:04.208703+0800 foobar[46578:1184250] > -[AppDelegate applicationShouldHandleReopen:hasVisibleWindows:] <NSApplication: 0x1004148c0> 0
2020-09-01 15:41:04.368730+0800 foobar[46578:1184250] > -[AppDelegate applicationShouldHandleReopen:hasVisibleWindows:] <NSApplication: 0x1004148c0> 0
2020-09-01 15:41:04.520342+0800 foobar[46578:1184250] > -[AppDelegate applicationShouldHandleReopen:hasVisibleWindows:] <NSApplication: 0x1004148c0> 0
2020-09-01 15:41:05.335217+0800 foobar[46578:1184250] > -[AppDelegate applicationDockMenu:] <NSApplication: 0x1004148c0>
2020-09-01 15:41:08.501825+0800 foobar[46578:1184250] > -[AppDelegate applicationShouldHandleReopen:hasVisibleWindows:] <NSApplication: 0x1004148c0> 0
2020-09-01 15:41:09.110916+0800 foobar[46578:1184250] > -[AppDelegate applicationDockMenu:] <NSApplication: 0x1004148c0>
Process 46578 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGKILL
frame #0: 0x00007fff68b0c11a libsystem_kernel.dylib`mach_msg_trap + 10
libsystem_kernel.dylib`mach_msg_trap:
-> 0x7fff68b0c11a <+10>: retq
0x7fff68b0c11b <+11>: nop
libsystem_kernel.dylib`mach_msg_overwrite_trap:
0x7fff68b0c11c <+0>: movq %rcx, %r10
0x7fff68b0c11f <+3>: movl $0x1000020, %eax ; imm = 0x1000020
Target 0: (foobar) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGKILL
* frame #0: 0x00007fff68b0c11a libsystem_kernel.dylib`mach_msg_trap + 10
frame #1: 0x00007fff68b0c490 libsystem_kernel.dylib`mach_msg + 60
frame #2: 0x00007fff2d6cf4e5 CoreFoundation`__CFRunLoopServiceMachPort + 247
frame #3: 0x00007fff2d6cdfb2 CoreFoundation`__CFRunLoopRun + 1319
frame #4: 0x00007fff2d6cd42e CoreFoundation`CFRunLoopRunSpecific + 462
frame #5: 0x00007fff2c314c0d HIToolbox`RunCurrentEventLoopInMode + 292
frame #6: 0x00007fff2c314925 HIToolbox`ReceiveNextEventCommon + 584
frame #7: 0x00007fff2c3146c9 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 64
frame #8: 0x00007fff2a972509 AppKit`_DPSNextEvent + 883
frame #9: 0x00007fff2a970d50 AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1352
frame #10: 0x00007fff2a962a5e AppKit`-[NSApplication run] + 658
frame #11: 0x00007fff2a934876 AppKit`NSApplicationMain + 777
frame #12: 0x0000000100000eaf foobar`main + 47
frame #13: 0x00007fff689cacc9 libdyld.dylib`start + 1
frame #14: 0x00007fff689cacc9 libdyld.dylib`start + 1
(lldb) ^D
Solution
47,50d46
< <key>NSSupportsSuddenTermination</key>
< <true/>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
MainMenu.xibwith aDelegateobject