RasterExRatatui.Framebuffer (RasterExRatatui v0.4.0)

Copy Markdown View Source

Writes packed pixels to a Linux framebuffer device (/dev/fbN).

fbdev needs no driver code on the Elixir side: the kernel exposes the display as a file, /sys/class/graphics/fbN describes its geometry, and writing bytes at the right offset lights the pixels.

{:ok, fb} = Framebuffer.open("fb0")
{:ok, format} = Framebuffer.format_for(fb.info)
raster = Raster.new(size: {fb.info.width, fb.info.height}, format: format, scale: 3)
# … on every push:
:ok = Framebuffer.write(fb, patches)

Stride

A framebuffer line may be longer than width * bytes_per_pixel (the driver pads it to an alignment). Patches carry no padding, so write/2 places every patch row at y * stride + x * bytes_per_pixel, and a full frame is written row by row when the stride differs from the visible line.

Consoles

The kernel's framebuffer console (fbcon) draws on the same device, so text and a blinking cursor can appear over the app. unbind_console/2 detaches it; moving the system console off the display (a serial ctty on Nerves) keeps the boot log away too.

Options

Every function that touches the filesystem accepts :root (default "/"), prepended to /sys/... and /dev/.... It exists for tests and for devices that mount sysfs elsewhere.

Summary

Types

Framebuffer geometry, from sysfs.

t()

Functions

The largest integer font scale that keeps at least columns: (default 100) cells of the font (default RasterExRatatui.Font.Default6x8) along the panel's long side, and never less than 1.

Blanks (true) or unblanks (false) the display through /sys/class/graphics/<name>/blank.

Closes the device.

The pixel format for a framebuffer's depth: RGB565 at 16 bits, XRGB8888 at 32.

Reads the geometry of framebuffer name ("fb0") from /sys/class/graphics/<name>: virtual_size, bits_per_pixel, and stride.

Reads the geometry of framebuffer name and opens /dev/<name> for writing.

Detaches the kernel's framebuffer console so it stops drawing text and a cursor over the display.

Writes a list of patches, or a {:frame, binary} covering the whole display, in one :file.pwrite/2 call.

Types

info()

@type info() :: %{
  width: pos_integer(),
  height: pos_integer(),
  bits_per_pixel: pos_integer(),
  stride: pos_integer()
}

Framebuffer geometry, from sysfs.

t()

@type t() :: %RasterExRatatui.Framebuffer{
  device: :file.io_device(),
  info: info(),
  name: String.t()
}

Functions

auto_scale(arg, opts \\ [])

@spec auto_scale({pos_integer(), pos_integer()}, keyword()) :: pos_integer()

The largest integer font scale that keeps at least columns: (default 100) cells of the font (default RasterExRatatui.Font.Default6x8) along the panel's long side, and never less than 1.

The long side is the same whichever way the panel is rotated, so this holds for the app's logical image too.

Examples

The Touch Display 2, 720×1280, gets 12×16 pixel cells: 60×80 in portrait, 106×45 turned; a 1080p monitor gets 18×24 pixel cells, 106×45.

iex> RasterExRatatui.Framebuffer.auto_scale({720, 1280})
2

iex> RasterExRatatui.Framebuffer.auto_scale({1920, 1080})
3

iex> RasterExRatatui.Framebuffer.auto_scale({3840, 2160}, columns: 200)
3

iex> RasterExRatatui.Framebuffer.auto_scale({480, 320})
1

blank(name, blank?, opts \\ [])

@spec blank(String.t(), boolean(), keyword()) :: :ok | {:error, term()}

Blanks (true) or unblanks (false) the display through /sys/class/graphics/<name>/blank.

close(framebuffer)

@spec close(t()) :: :ok | {:error, term()}

Closes the device.

format_for(map)

@spec format_for(%{bits_per_pixel: pos_integer()}) ::
  {:ok, module()} | {:error, :unsupported}

The pixel format for a framebuffer's depth: RGB565 at 16 bits, XRGB8888 at 32.

Examples

iex> RasterExRatatui.Framebuffer.format_for(%{bits_per_pixel: 32})
{:ok, RasterExRatatui.PixelFormat.XRGB8888}

iex> RasterExRatatui.Framebuffer.format_for(%{bits_per_pixel: 16})
{:ok, RasterExRatatui.PixelFormat.RGB565}

iex> RasterExRatatui.Framebuffer.format_for(%{bits_per_pixel: 24})
{:error, :unsupported}

info(name, opts \\ [])

@spec info(String.t(), keyword()) :: {:ok, info()} | {:error, term()}

Reads the geometry of framebuffer name ("fb0") from /sys/class/graphics/<name>: virtual_size, bits_per_pixel, and stride.

Returns {:error, reason} when a file is missing or does not hold what fbdev writes there.

open(name, opts \\ [])

@spec open(String.t(), keyword()) :: {:ok, t()} | {:error, term()}

Reads the geometry of framebuffer name and opens /dev/<name> for writing.

unbind_console(console, opts \\ [])

@spec unbind_console(String.t(), keyword()) :: :ok | {:error, term()}

Detaches the kernel's framebuffer console so it stops drawing text and a cursor over the display.

Writes 0 to /sys/class/vtconsole/<console>/bind. The framebuffer console is usually "vtcon1" ("vtcon0" is the dummy console); /sys/class/vtconsole/*/name tells them apart. Best effort: returns {:error, reason} when the kernel has no such console.

write(framebuffer, pixels)

@spec write(t(), [RasterExRatatui.Patch.t()] | {:frame, binary()}) ::
  :ok | {:error, term()}

Writes a list of patches, or a {:frame, binary} covering the whole display, in one :file.pwrite/2 call.