Lightweight windowing and event handling for macOS, built in C with a Cocoa backend.
Canopy is a simple windowing and input library for macOS. It is written in C with a native Objective-C backend and uses Cocoa directly. No third-party dependencies.
This project is educational and experimental in nature. It’s not intended for production or anything like that. I just want to better understand macOS internals and build something from scratch.
Warning
This project is under active development and is evolving quickly. Features, function names, and structures may change at any time. Breaking changes are likely.
- Create and manage native macOS windows
- Handle mouse, keyboard, and scroll input
- Provide a backbuffer and rendering pipeline
- Add a logging system (broke this out as BlackBox)
- Enable hardware acceleration
- Add sprite support and simple 2D rendering helpers
- macOS (tested on Monterey and later)
- Clang (or Xcode CLI tools)
- C99-compatible compiler
git clone <link>
make installAnd it will be installed on your system.
(Examples and demos to come)
/*******************************************************************************
*
* CANOPY [Example] - Custom Framebuffer Rendering
*
* Description:
* Renders a solid-colored rectangle using a manually created framebuffer.
* Demonstrates how to allocate, fill, and display a custom framebuffer.
* Assumes installation of Canopy and BlackBox.
* Error checking is kept to a minimum for brevity.
*
* Controls:
* [Close Window] - Exit application
*
*******************************************************************************/
#include <canopy.h>
#include <blackbox.h>
#define WIDTH 400
#define HEIGHT 400
// Phthalo Blue
// https://colors.artyclick.com/color-names-dictionary/color-names/phthalo-blue-color
// little endian, abgr (rgba backwards)
#define PHTALO_BLUE 0xff890f00
#define CANOPY_BLUE 0xffff0000
int main(void)
{
// Initialization
//--------------------------------------------------------------------------
init_log(LOG_DEFAULT);
Window* win = create_window("Canopy - Custom Framebuffer",
WIDTH, HEIGHT,
CANOPY_WINDOW_STYLE_DEFAULT);
/* get_framebuffer(win) would return the framebuffer to the window, allowing
* direct manipulation. This is the other way, for situations where it is
* best to finish creating the buffer, and swap.
* [!NOTE] you can not use WIDTH, HEIGHT because of content scaling */
framebuffer fb;
get_framebuffer_size(win, &fb.width, &fb.height);
int pixels_arr = fb.width * fb.height;
fb.pitch = fb.width * CANOPY_BYTES_PER_PIXEL;
size_t buffer_size = fb.pitch * fb.height;
fb.pixels = canopy_malloc(buffer_size);
//--------------------------------------------------------------------------
// Main Game Loop
while (!window_should_close(win))
{
// Update
pump_messages();
//----------------------------------------------------------------------
/* `dispatch_events(win)` allows you to create custom callbacks for both
* key, mouse and text events that handles everything. If not, doing it
* manually like this also works */
canopy_event event;
while (poll_event(&event))
{
// Handle events (mouse, keyboard, etc.)
}
//----------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------
if (should_render_frame())
{
// Fill the framebuffer with its clear color
for (int i = 0; i < pixels_arr; ++i) {
if(i >= pixels_arr/2){
fb.pixels[i] = CANOPY_BLUE;
}
else{
fb.pixels[i] = PHTALO_BLUE;
}
}
// Do other stuff graphicly here
swap_backbuffer(win,&fb); // Switch pointers of custom framebuffer
present_buffer(win); // Present on screen
}
//----------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------
free(fb.pixels);
free_window(win);
shutdown_log();
//--------------------------------------------------------------------------
return 0;
}Use clang to build:
clang main.c src/canopy.m src/canopy_event.c src/canopy_time.c \
-framework Cocoa -I. -Ilib -lblackbox -o bin/testOr integrate it into your own CMake or Makefile setup.
This is a side project, built for fun and learning.
If you’re interested in how to work directly with Cocoa and C, Canopy might be a good reference.
Feel free to use, just don’t expect it to replace SDL, Raylib or GLFW (yet)!
