Skip to content

Instantly share code, notes, and snippets.

@peterc
Created February 4, 2026 20:26
Show Gist options
  • Select an option

  • Save peterc/64b2e96f5857c6d777656ba3389faff0 to your computer and use it in GitHub Desktop.

Select an option

Save peterc/64b2e96f5857c6d777656ba3389faff0 to your computer and use it in GitHub Desktop.
A macOS app entirely in plain C
// A macOS GUI app written in pure C, using the Objective-C runtime directly.
// No Objective-C syntax anywhere. Just raw objc_msgSend and runtime calls.
// This is cursed. Enjoy.
// Compile with clang -Wall -Wextra -o hello_runtime hello_runtime.c \
// -framework Cocoa -lobjc
#include <objc/objc.h>
#include <objc/message.h>
#include <objc/runtime.h>
#include <stdbool.h>
typedef struct { double x, y, width, height; } CGRect;
typedef unsigned long NSUInteger;
// Every objc_msgSend call needs a cast to the correct function pointer type.
// These typedefs save some sanity.
typedef id (*send_t)(id, SEL);
typedef id (*send_id_t)(id, SEL, id);
typedef id (*send_rect_t)(id, SEL, CGRect);
typedef id (*send_long_t)(id, SEL, long);
typedef id (*send_double_t)(id, SEL, double);
typedef id (*send_bool_t)(id, SEL, bool);
typedef id (*send_cstr_t)(id, SEL, const char *);
typedef id (*send_init_window_t)(id, SEL, CGRect, NSUInteger, NSUInteger, bool);
typedef void (*send_void_t)(id, SEL);
typedef void (*send_void_id_t)(id, SEL, id);
typedef void (*send_void_bool_t)(id, SEL, bool);
typedef void (*send_void_long_t)(id, SEL, long);
#define cls(name) (id)objc_getClass(name)
#define sel(name) sel_registerName(name)
// Delegate callback: quit when the window closes
static BOOL should_terminate(id self, SEL cmd, id sender) {
(void)self; (void)cmd; (void)sender;
return YES;
}
int main(void) {
// NSApplication *app = [NSApplication sharedApplication];
id app = ((send_t)objc_msgSend)(cls("NSApplication"), sel("sharedApplication"));
// [app setActivationPolicy:NSApplicationActivationPolicyRegular];
((send_void_long_t)objc_msgSend)(app, sel("setActivationPolicy:"), 0);
// --- Menu bar (so Cmd+Q works) ---
// NSMenu *menubar = [[NSMenu alloc] init];
id menubar = ((send_t)objc_msgSend)(cls("NSMenu"), sel("alloc"));
menubar = ((send_t)objc_msgSend)(menubar, sel("init"));
// NSMenuItem *appMenuItem = [[NSMenuItem alloc] init];
id app_menu_item = ((send_t)objc_msgSend)(cls("NSMenuItem"), sel("alloc"));
app_menu_item = ((send_t)objc_msgSend)(app_menu_item, sel("init"));
((send_void_id_t)objc_msgSend)(menubar, sel("addItem:"), app_menu_item);
// NSMenu *appMenu = [[NSMenu alloc] init];
id app_menu = ((send_t)objc_msgSend)(cls("NSMenu"), sel("alloc"));
app_menu = ((send_t)objc_msgSend)(app_menu, sel("init"));
// NSString *quitTitle = @"Quit";
id quit_title = ((send_cstr_t)objc_msgSend)(cls("NSString"), sel("stringWithUTF8String:"), "Quit");
id quit_key = ((send_cstr_t)objc_msgSend)(cls("NSString"), sel("stringWithUTF8String:"), "q");
// NSMenuItem *quitItem = [[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
id quit_item = ((send_t)objc_msgSend)(cls("NSMenuItem"), sel("alloc"));
quit_item = ((id (*)(id, SEL, id, SEL, id))objc_msgSend)(
quit_item, sel("initWithTitle:action:keyEquivalent:"),
quit_title, sel("terminate:"), quit_key
);
((send_void_id_t)objc_msgSend)(app_menu, sel("addItem:"), quit_item);
((send_void_id_t)objc_msgSend)(app_menu_item, sel("setSubmenu:"), app_menu);
((send_void_id_t)objc_msgSend)(app, sel("setMainMenu:"), menubar);
// --- App delegate (so closing the window quits the app) ---
// Create a new class at runtime: @interface AppDelegate : NSObject <NSApplicationDelegate>
Class delegate_class = objc_allocateClassPair(
(Class)objc_getClass("NSObject"), "AppDelegate", 0
);
// Add method: - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
class_addMethod(delegate_class,
sel("applicationShouldTerminateAfterLastWindowClosed:"),
(IMP)should_terminate, "B@:@"
);
objc_registerClassPair(delegate_class);
id delegate = ((send_t)objc_msgSend)((id)delegate_class, sel("alloc"));
delegate = ((send_t)objc_msgSend)(delegate, sel("init"));
((send_void_id_t)objc_msgSend)(app, sel("setDelegate:"), delegate);
// --- Window ---
// NSWindow *window = [[NSWindow alloc] initWithContentRect:... styleMask:... backing:... defer:NO];
id window = ((send_t)objc_msgSend)(cls("NSWindow"), sel("alloc"));
window = ((send_init_window_t)objc_msgSend)(
window, sel("initWithContentRect:styleMask:backing:defer:"),
(CGRect){200, 200, 500, 300},
15, // NSWindowStyleMaskTitled | Closable | Miniaturizable | Resizable
2, // NSBackingStoreBuffered
false
);
// [window setTitle:@"Hello from Pure C"];
id title = ((send_cstr_t)objc_msgSend)(cls("NSString"), sel("stringWithUTF8String:"), "Hello from Pure C");
((send_void_id_t)objc_msgSend)(window, sel("setTitle:"), title);
// --- Text label ---
// NSTextField *label = [[NSTextField alloc] initWithFrame:...];
id label = ((send_t)objc_msgSend)(cls("NSTextField"), sel("alloc"));
label = ((send_rect_t)objc_msgSend)(label, sel("initWithFrame:"), (CGRect){50, 100, 400, 80});
// [label setStringValue:@"Hello, World!\nWritten in pure C.\nNo Objective-C syntax. Just vibes."];
id text = ((send_cstr_t)objc_msgSend)(cls("NSString"),
sel("stringWithUTF8String:"),
"Hello, World!\nWritten in pure C.\nNo Objective-C syntax. Just vibes."
);
((send_void_id_t)objc_msgSend)(label, sel("setStringValue:"), text);
// Make it look like a label, not a text field
((send_void_bool_t)objc_msgSend)(label, sel("setBezeled:"), false);
((send_void_bool_t)objc_msgSend)(label, sel("setDrawsBackground:"), false);
((send_void_bool_t)objc_msgSend)(label, sel("setEditable:"), false);
((send_void_bool_t)objc_msgSend)(label, sel("setSelectable:"), false);
// [label setFont:[NSFont systemFontOfSize:24]];
id font = ((send_double_t)objc_msgSend)(cls("NSFont"), sel("systemFontOfSize:"), 24.0);
((send_void_id_t)objc_msgSend)(label, sel("setFont:"), font);
// [[window contentView] addSubview:label];
id content_view = ((send_t)objc_msgSend)(window, sel("contentView"));
((send_void_id_t)objc_msgSend)(content_view, sel("addSubview:"), label);
// --- Show and run ---
// [window makeKeyAndOrderFront:nil];
((send_void_id_t)objc_msgSend)(window, sel("makeKeyAndOrderFront:"), nil);
// [app activateIgnoringOtherApps:YES];
((send_void_bool_t)objc_msgSend)(app, sel("activateIgnoringOtherApps:"), true);
// [app run];
((send_void_t)objc_msgSend)(app, sel("run"));
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment