BootCanvas is a bare-metal x86 operating system kernel built from scratch in C and assembly. It boots via BIOS, enters protected mode, initializes a linear framebuffer, and provides a simple graphical environment with a console, keyboard/mouse input, and small GUI programs.
This project is not a general-purpose Linux-like OS. It is a compact kernel-focused environment designed to explore low-level bootstrapping, protected mode setup, interrupt handling, graphics primitives, and user-interface code running directly on hardware.
qemu.mp4
This is a screen recording of BootCanvas running on QEMU, a free and open-source machine emulator.
BootCanvas combines several low-level pieces into a functioning bootable kernel image:
- BIOS boot sector that loads the kernel from disk
- 32-bit protected mode startup
- Global Descriptor Table (GDT) setup
- Interrupt Descriptor Table (IDT) setup
- PIC remapping for hardware IRQs
- PS/2 keyboard and mouse drivers
- VESA framebuffer graphics
- 2D drawing primitives: pixels, lines, rectangles, ellipses, text
- Console shell with commands
- Windowed GUI programs, including a paint-style app and a calculator GUI program
- A minimal custom libc subset for kernel code
BootCanvas focuses on the kernel rather than a general-purpose desktop environment.
The repository is organized as follows:
BootCanvas/
├── Makefile
├── linker.ld
├── README.md
├── LICENSE
├── .gitignore
├── src/
│ ├── boot.asm # BIOS bootloader stage
│ ├── kernel.asm # Protected mode kernel entry and VESA setup
│ ├── main.c # Kernel entry point
│ ├── arch/
│ │ ├── gdt.asm # GDT definitions
│ │ ├── idt.asm # IDT setup and interrupt gate loading
│ │ ├── isr_stubs.asm # Keyboard and mouse interrupt stubs
│ │ ├── interrupts.c # PIC remap / EOI handling
│ │ ├── interrupts.h
│ │ ├── io.h # inb/outb helpers and HLT wrapper
│ │ ├── program.c # current program dispatcher
│ │ └── program.h
│ ├── drivers/
│ │ ├── font.h # 8x8 bitmap font data
│ │ ├── graphics.c # framebuffer and drawing functions
│ │ ├── graphics.h
│ │ ├── keyboard.c # PS/2 keyboard handling
│ │ ├── keyboard.h
│ │ ├── mouse.c # PS/2 mouse packet parsing
│ │ ├── mouse.h
│ │ └── scancode.h
│ ├── gui/
│ │ ├── gui.c # UI helpers and buttons
│ │ └── gui.h
│ ├── libc/
│ │ ├── conio.c # console I/O for kernel
│ │ ├── ctype.c
│ │ ├── math.c
│ │ ├── stdlib.c
│ │ ├── string.c
│ │ └── include/
│ │ ├── conio.h
│ │ ├── ctype.h
│ │ ├── math.h
│ │ ├── stdlib.h
│ │ └── string.h
│ ├── programs/
│ │ ├── console.c # shell and command dispatcher
│ │ ├── console.h
│ │ ├── paint_gui.c # paint-like drawing app
│ │ ├── paint_gui.h
│ │ ├── calc_gui.c # calculator app
│ │ ├── calc_gui.h
│ │ ├── mouse.c # mouse-related program utilities
│ │ └── mouse.h
│ └── main.c # entry and event loop
└── linker.ld # ELF linker script for kernel
The boot sequence is:
- BIOS loads the first sector into memory at
0x7C00. src/boot.asmruns in real mode.- It:
- saves the boot drive number from
DL - clears segment registers
- sets up a stack
- enables A20
- uses BIOS
INT 13hto read the kernel from disk - jumps to
0x0000:0x1000
- saves the boot drive number from
- The kernel is loaded after the bootloader sector, and control transfers into the kernel entry.
src/kernel.asmthen:- loads the GDT
- queries VESA mode info
- switches to 1024x768, 24-bit color mode
- enables protected mode
- sets CR0 and CR4 bits for protected mode and SSE
- sets up the stack
- calls
kmain
The linker script (linker.ld) defines the kernel layout and sets the entry point to kstart16, with the runtime memory image spanning from 0x1000 upward.
The kernel uses a flat memory model with a 32-bit protected mode environment.
Important kernel startup details:
GDT_CODE_SELandGDT_DATA_SELare used to move from real mode to protected modecr0has the PE bit set to enable protected modecr4hasOSFXSRandOSXMMEXCPTset to enable SSE/SSE2fninitresets x87 floating-point statelgdtloads the global descriptor tablelidtis used later to install the interrupt descriptor table
The kernel configures CPU state directly during startup.
The graphics system is built around a linear framebuffer provided by VESA BIOS extensions.
The kernel obtains:
- framebuffer pointer
- width
- height
- bits per pixel
- pitch
This is passed into init_graphics() from src/main.c and stored in a GraphicsState struct.
Relevant graphics primitives include:
plot_pixel()get_pixel()line()rectangle()filled_rectangle()ellipse()put_char()put_text()clear()swap_buffers()
The graphics driver stores a backbuffer in memory and copies it to the framebuffer on each frame tick. This allows drawing operations to be staged before visible presentation.
The project also includes a simple bitmap font in src/drivers/font.h, which is used to render text directly to the framebuffer.
The kernel configures the chipset interrupt system directly:
- keyboard IRQ is mapped to vector
0x21 - mouse IRQ is mapped to vector
0x2C - the master and slave PIC are remapped away from the CPU exception vectors
- the IDT is loaded with handlers for the enabled IRQs
The interrupt handling path is:
setup_interrupts()insrc/arch/idt.asmremap_pic()insrc/arch/interrupts.cirq1_keyboard_stub/irq12_mouse_stubinsrc/arch/isr_stubs.asmkeyboard_handler()/mouse_handler()
Keyboard support is implemented with standard PS/2 controller communication:
- controller input/output port access via
0x64and0x60 - scan-code decoding
- modifier tracking for:
- shift
- control
- alt
- caps lock
- conversion to ASCII with shifted/unshifted mappings
- event toggling to detect input changes without polling too aggressively
The keyboard driver tracks:
- last scancode
- key pressed state
- event_toggle
and exposes a KeyboardState structure.
The mouse driver parses standard PS/2 packet data:
- left button
- right button
- middle button
- x delta
- y delta
Mouse movement is clamped to the current framebuffer dimensions and stored in MouseState:
typedef struct {
int32_t x;
int32_t y;
bool left_button;
bool middle_button;
bool right_button;
} MouseState;The main kernel loop polls for hardware changes and then calls the active program.
src/main.c is the primary kernel entry point:
void kmain(uint32_t framebuffer, uint32_t width, uint32_t height, uint32_t bpp,
uint32_t pitch) {
init_graphics(framebuffer, width, height, bpp, pitch);
init_console();
setup_interrupts();
init_program();
set_current_program((Program){NULL, CONSOLE_run});
while (1) {
wait_for_interrupts();
MOUSE_restore_bg();
run_program();
MOUSE_run();
swap_buffers();
}
}The project uses a simple program-switching model:
Programcontains:init()run(bool keyboard_changed, bool mouse_changed)
set_current_program()swaps the active runtime programrun_program()checks for keyboard/mouse changes and calls the active program
This allows the kernel to switch between a shell and GUI programs without any real OS scheduler.
The console is the first active program launched after the kernel initializes.
The shell supports commands such as:
helpclear/clsecho <text>aboutpaint/drawcalc/calculator
The console is implemented in src/programs/console.c and uses a custom kernel console I/O layer.
Examples from the code:
helpprints the command listaboutprints the kernel version and display resolutionpaintswitches to the paint GUI programcalcswitches to the calculator GUI program
The shell is a minimal command environment, not a full TTY or Unix shell.
The paint-style program is implemented in src/programs/paint_gui.c.
It provides:
- tool sidebar
- color palette
- pencil tool
- line tool
- rectangle tool
- ellipse tool
- clear canvas
- exit to shell
The drawing area is defined by:
static Point g_canvas_tl = {170, 10};
static Point g_canvas_br = {790, 590};The app uses the same framebuffer drawing primitive layer as the rest of the kernel and is event-driven from mouse state.
The UI is composed using helper functions in src/gui/gui.c, including:
ui_draw_panel()ui_draw_window_frame()ui_draw_button()ui_button_clicked()
The paint program uses these helpers to draw its panels, window frame, and buttons.
A calculator GUI program exists in:
src/programs/calc_gui.csrc/programs/calc_gui.h
It is launched from the console with:
calccalculator
The calculator runs as a kernel program and is switched to from the console.
The kernel includes a small custom standard library layer for low-level C code:
src/libc/ctype.candctype.hsrc/libc/string.candstring.hsrc/libc/stdlib.candstdlib.hsrc/libc/conio.candconio.hsrc/libc/math.candmath.h
This is not a standard libc implementation. It is a compact set of utilities intended for kernel-level development.
Notable functions include:
itoa()- string comparison / length utilities
isspace()-style classification- console output helpers
- numeric conversion utilities
The build uses these headers as part of the kernel compilation pipeline.
The project is built using Makefile.
Key build targets:
makeormake allbuilds the OS imagemake runlaunches the OS in QEMUmake cleanremoves the build directorymake compile_commands.jsongenerates compilation metadata whencompiledbis installed
The build creates:
build/boot.binbuild/kernel.elfbuild/kernel.binbuild/os.img
The bootloader uses sed to patch the sector count into boot.asm dynamically:
SECTORS=$$(( ( $$(stat -c%s $(KERNEL_BIN)) + 511 ) / 512 )) ; \
sed "s/__NUM_SECTORS__/$${SECTORS}/" $(BOOT_SRC) > $(BOOT_TMP)This patches the kernel's sector count into the bootloader so the BIOS reads the required number of sectors.
Requirements:
- NASM
- GCC in 32-bit mode (
-m32) ldobjcopy- QEMU (
qemu-system-x86_64)
Build:
git clone https://github.com/pxrxp/BootCanvas.git
cd BootCanvas
makeRun in QEMU:
make runThis runs:
qemu-system-x86_64 -drive format=raw,file=build/os.img -no-reboot -d int,cpu_reset -D build/qemu.logIf you want to start directly without the make target:
qemu-system-x86_64 -drive format=raw,file=build/os.imgThe system currently provides:
- raw kernel boot
- protected mode CPU startup
- direct hardware access
- framebuffer graphics
- keyboard and mouse drivers
- a small command shell
- simple GUI programs
It does not currently provide:
- paging
- virtual memory
- preemptive multitasking
- file system
- network stack
- a true user-space process model
- a robust kernel API
- a fully general-purpose desktop environment
This project is distributed under the MIT License. See LICENSE for details.
