// lesson: you-own-every-pixel
You Own Every Pixel
Open TextEdit or Notepad and it looks like the operating system is doing the
work: there's a window, there's text, the caret blinks. It is tempting to
imagine an OS call like os_create_text_editor(). There isn't one. What the
OS actually gives you is astonishingly little:
- a window โ a rectangle of pixels it agrees to composite onto the screen for you, and
- an event stream โ a queue of "the user pressed J", "the mouse moved to (412, 88)", "your window is now 800ร600", "please redraw yourself".
Everything else โ every glyph, the caret, the selection highlight, the
scrollbar โ is pixels you computed and handed over. (Toolkit widgets like
GTK's TextView or Win32's EDIT control are libraries layered on exactly
this; in this course we live at the layer they're built on. If you've
wondered how those widgets could ever be written, this is how.)
That inversion is the first thing to internalize. A command-line program calls the OS when it wants something. A GUI program is called by the OS โ or more precisely, it spends its life in a loop asking "what happened?" and reacting:
// The heart of every GUI program ever written, on every platform:
while (running) {
Event ev = wait_for_next_event(); // blocks until something happens
handle(ev); // mutate state, maybe draw
}
This is the event loop. X11 spells it xcb_wait_for_event; Win32
spells it GetMessage/DispatchMessage; Cocoa hides it inside
[NSApplication run]. The names differ, the shape never does. Your editor
will be a pure function of the events it has received โ which is also what
will make it testable: feed it a scripted list of events and assert on
the state, no screen required. Every graded challenge in this course works
that way, and the final challenge drives a whole editor core with synthetic
keystrokes and mouse clicks.
The event queue is a real queue
Events arrive faster than you handle them โ the X server doesn't wait for you. They pile up in a queue, and that queue has two properties worth respecting from day one:
- Order matters. A
MouseDownat (10,10) followed byMouseMoveto (50,10) is a drag. Reordered, it's nonsense. Events are handled strictly first-in, first-out. - Some events are collapsible. If the user sweeps the mouse across the
window, you might receive 300
MouseMoveevents in one frame. Nothing observable depends on the intermediate positions โ only the latest one. Handling all 300 (each triggering hit-testing and maybe a repaint) is how editors get laggy under fast mouse movement. The same goes for paint requests: if the OS asked you to redraw three times before you got around to it, you redraw once. Real platforms bake this in โ Win32 coalescesWM_PAINTandWM_MOUSEMOVEin the queue itself; X11 clients compress runs ofMotionNotifyby hand, exactly like you're about to.
One rule keeps coalescing honest: only adjacent events of the same kind
may collapse. A MouseMove, MouseDown, MouseMove sequence must survive
intact โ collapsing across the click would eat a drag gesture.
Quit is an event too
There is no OS callback that kills your process when the user clicks the
close button (on X11 and Cocoa at least โ we'll meet the details in lesson
3). You receive an event asking you to close, and you decide what to do:
save prompts, veto, or exit the loop. That's why the loop condition is
running and not true โ quitting is just the one event that makes you
stop pumping.
โบ Pump the Queue
12 ptsModel the innermost piece of the editor: draining one batch of queued
events. Implement pump, which takes the queued events in arrival order
and decides which ones actually get delivered to the application:
- FIFO: delivered events keep their relative order.
- Coalescing: in any run of consecutive
MouseMoveevents, deliver only the last one. In any run of consecutivePaintevents, deliver only the last one. Runs are broken by any event of a different type โ never coalesce across an intervening event. - Quit: when a
Quitevent is reached, setquit = trueand stop.Quititself is not delivered, and events after it are never examined โ not even for coalescing.
This is exactly the shape of the drain loop you'll later wrap around
xcb_poll_for_event: pull everything that's pending, compress it, then act.
Log in to submit a solution and earn points.