// 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 MouseDown at (10,10) followed by MouseMove to (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 MouseMove events 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 coalesces WM_PAINT and WM_MOUSEMOVE in the queue itself; X11 clients compress runs of MotionNotify by 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 pts

Model 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 MouseMove events, deliver only the last one. In any run of consecutive Paint events, deliver only the last one. Runs are broken by any event of a different type โ€” never coalesce across an intervening event.
  • Quit: when a Quit event is reached, set quit = true and stop. Quit itself 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.