defmodule Framebuffer do @moduledoc """ Abstraction over Linux framebuffer devices. ## Linux headers - [linux/fb.h](https://github.com/torvalds/linux/blob/master/include/linux/fb.h) - [uapi/linux/fb.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/fb.h) ## Linux documentation - [fb device API](https://github.com/torvalds/linux/blob/master/Documentation/fb/api.rst) - [fb driver API](https://github.com/torvalds/linux/blob/master/Documentation/driver-api/frame-buffer.rst) """ defstruct [ :ref, :fix_screeninfo, :var_screeninfo ] @type t() :: %__MODULE__{ ref: reference(), fix_screeninfo: Framebuffer.Screeninfo.Fix.t(), var_screeninfo: Framebuffer.Screeninfo.Var.t() } @type device_t() :: Path.t() @type pixel_t() :: {x(), y(), color()} @type x() :: non_neg_integer() @type y() :: non_neg_integer() @typedoc "Color: {red, green, blue}" @type color() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()} @doc """ Opens a framebuffer device and returns an `:ok` tuple with a `t:Framebuffer.t/0` including fixed and variable device information and a reference to an open file descriptor. This file descriptor is kept open for the lifetime of the reference. ## Arguments | parameter | required | default | | --------- | -------- | -------- | | device | false | /dev/fb0 | """ @spec open(device_t()) :: {:ok, Framebuffer.t()} | {:error, term()} def open(device \\ "/dev/fb0"), do: Framebuffer.NIF.open(device) @doc """ Optimistic version of `open/1`. Raises on any error. """ @spec open!(device_t()) :: Framebuffer.t() | no_return() def open!(device \\ "/dev/fb0") do case Framebuffer.NIF.open(device) do {:ok, framebuffer} -> framebuffer {:error, error} -> raise(error) end end @doc """ Given an open framebuffer, refresh its fixed and variable device information. ## Arguments | parameter | required | default | | ----------- | -------- | ------- | | framebuffer | true | | """ @spec info(Framebuffer.t()) :: {:ok, Framebuffer.t()} | {:error, term()} def info(framebuffer), do: Framebuffer.NIF.info(framebuffer) @spec info!(Framebuffer.t()) :: Framebuffer.t() | no_return() def info!(framebuffer) do case Framebuffer.NIF.info(framebuffer) do {:ok, framebuffer} -> framebuffer {:error, error} -> raise(error) end end defimpl String.Chars do def to_string(framebuffer) do var = framebuffer.var_screeninfo """ mode "#{var.xres}x#{var.yres}" geometry #{var.xres} #{var.yres} #{var.xres_virtual} #{var.yres_virtual} #{var.bits_per_pixel} timings #{var.pixclock} #{var.left_margin} #{var.right_margin} #{var.upper_margin} #{var.lower_margin} #{var.hsync_len} #{var.vsync_len} nonstd #{var.nonstd} rgba #{var.red},#{var.green},#{var.blue},#{var.transp} endmode """ end end @spec put_pixel(Framebuffer.t(), x(), y(), color()) :: :ok | {:error, term()} def put_pixel(framebuffer, x, y, color) do cond do x >= framebuffer.var_screeninfo.xres -> {:error, "out of bounds"} y >= framebuffer.var_screeninfo.yres -> {:error, "out of bounds"} true -> Framebuffer.NIF.put_pixel(framebuffer, x, y, color) end end @spec clear(Framebuffer.t()) :: {:ok, Framebuffer.t()} def clear(framebuffer) do zero = {0, 0, 0} framebuffer |> to_stream() |> Stream.each(fn {x, y} -> :ok = Framebuffer.NIF.put_pixel(framebuffer, x, y, zero) end) |> Stream.run() {:ok, framebuffer} end @spec rand(Framebuffer.t()) :: {:ok, Framebuffer.t()} def rand(framebuffer) do framebuffer |> to_stream() |> Stream.each(fn {x, y} -> color = {:rand.uniform(256) - 1, :rand.uniform(256) - 1, :rand.uniform(256) - 1} :ok = Framebuffer.NIF.put_pixel(framebuffer, x, y, color) end) |> Stream.run() {:ok, framebuffer} end defp to_stream(framebuffer) do xlimit = framebuffer.var_screeninfo.xres - 1 ylimit = framebuffer.var_screeninfo.yres - 1 Stream.unfold( {0, 0}, fn {^xlimit, ^ylimit} -> nil {^xlimit, y} = prev -> next = {0, y + 1} {prev, next} {x, y} = prev -> next = {x + 1, y} {prev, next} end ) end end