termcast

package module
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 5, 2026 License: MIT Imports: 20 Imported by: 0

README

chromedp logo

About termcast

termcast is a small Go package that streams the screen of a chromedp page to a terminal that has graphics. It uses the screencast of the Chrome DevTools Protocol to get the frames, and rasterm to draw them. It works in Kitty, iTerm2 and Sixel terminals. You start it with one call.

Unit Tests Go Reference Releases Discord Discussion

Install

The module needs Go 1.25 or later. It requires chromedp v0.20.0 and cdproto v0.157.8.

go get github.com/chromedp/termcast

Usage

Load the page first, so that the stream has a page to show. Start also starts the browser when the context has none. Then call Flags.Start or Start with the context. The stream stops when you call Stop, and when the context ends.

package main

import (
	"context"
	"flag"
	"log"
	"time"

	"github.com/chromedp/chromedp"
	"github.com/chromedp/termcast"
)

func main() {
	var tc termcast.Flags
	tc.Register(flag.CommandLine)
	verbose := flag.Bool("v", false, "print the protocol messages")
	flag.Parse()

	ctx, cancel := chromedp.NewContext(context.Background())
	defer cancel()

	// Load the page. This call starts the browser.
	if err := chromedp.Do(ctx, chromedp.Navigate("https://example.com")); err != nil {
		log.Fatal(err)
	}

	// s is nil when the flag -visible-on-terminal is off. A nil stream is
	// safe to use.
	s, err := tc.Start(ctx, *verbose)
	if err != nil {
		log.Fatal(err)
	}
	log.SetOutput(s.LogWriter())
	defer s.Stop()

	log.Println("the page is on the screen")
	if err := chromedp.Do(ctx, chromedp.Sleep(10*time.Second)); err != nil {
		s.Fatal(err)
	}
}

Run it with the flag:

go run . -visible-on-terminal

To start the stream with no flag, call termcast.Start(ctx, opts...).

How it works

The browser sends a frame only when the page changes. The stream keeps the latest frame. At each tick it clears the terminal and draws the latest frame, but only when the frame is new. A page that stops changing stays on the screen. A program can end before the browser sends the first frame. In that case Stop takes one screenshot, so that the final frame is never empty.

The stream clears the screen with \x1b[H\x1b[2J at each redraw. It uses no alternate screen and does not hide the cursor. A program that ends with log.Fatal skips its deferred calls, and the terminal must not stay in a changed state. See the decision.

Options

Option Default Meaning
WithFPS(fps) 4 The most frames per second that the stream draws
WithQuality(q) 80 The JPEG quality of the frames, from 0 to 100
WithMaxSize(w, h) 1024 by 768 The largest size of a frame in pixels
WithOutput(w) os.Stdout The writer that receives the frames
WithLogOutput(w) os.Stderr The writer that receives the held log lines
WithElement(sel) the whole page Draws only the element that the selector finds
WithEncoder(e) Kitty, iTerm2 or Sixel The rasterm.Encoder that draws a frame

Flags registers two flags: -visible-on-terminal, which turns the stream on, and -terminal-fps, which sets the frame rate and defaults to 4.

Programs that use it

The chromedp/examples repository has 34 programs that use termcast. Most of them take the flags -visible-on-terminal and -terminal-fps with Flags. The program termcast calls Start directly. It plays an animated SVG and streams it to the terminal. These programs use the package: click, console, cookie, dialogs, download_file, download_image, dragdrop, emulate, eval, eventsiter, exposefunc, extension, fast, forecast, frames, geoip, headers, intercept, keys, latlon, logic, pdf, pdfoptions, proxy, remote, screenshot, selectors, structeval, submit, subtree, termcast, text, upload and visible.

Show one element

WithElement takes a chromedp selector, such as chromedp.CSS("#chart"), and draws only the first element that it matches.

s, err := termcast.Start(ctx, termcast.WithElement(chromedp.CSS("#chart")))

The stream looks up the element again at each frame, so an element that moves or changes its size stays in view. It crops the frame to the border box of the element. The browser sends the viewport, so the stream shows the part of the element that is in the viewport. Use chromedp.ScrollIntoView first when the element is lower on the page. While the page has no such element, the stream keeps what is on the screen.

Terminals that work

The terminal must support one of these graphics protocols:

  • Kitty and Ghostty
  • iTerm2, WezTerm and mintty
  • Sixel

rasterm detects the protocol from the environment (TERM, TERM_PROGRAM, LC_TERMINAL and the variables of Kitty) and, for Sixel, with a query to the terminal. The variable TERM_GRAPHICS forces the protocol to kitty, iterm or sixel, and the value none turns graphics off. Start returns ErrNoGraphics when no protocol is available. Available reports the same thing before you start.

Log lines

A log line that the program prints draws over the frames, and the next redraw erases it. Stream.LogWriter returns a writer that holds the lines while the stream runs. When the stream stops, it draws a final frame and then prints the lines to the log output. Use log.SetOutput(s.LogWriter()). After Stop, the writer passes each line on at once.

Stream.Fatal stops the stream, prints the held lines, and then calls log.Fatal. Use it where a program calls log.Fatal, because log.Fatal ends the program before a deferred Stop runs.

Every method of *Stream is safe on a nil pointer. On a nil stream, Stop does nothing, Fatal calls log.Fatal and LogWriter returns os.Stderr.

The flag -v

The flag -v of a program prints the protocol messages. They draw over the frames, so the two do not work together. Flags.Start returns ErrVerbose when both are on.

Tests

The tests start Chrome and need no terminal. They use a fake encoder and a local page that animates. See AGENTS.md for the commands.

GOWORK=off go test -race ./...

Contributing

See CONTRIBUTING.md. Ask questions in the GitHub Discussions of chromedp, and report bugs in the issues of chromedp. The documents of this repository are AGENTS.md, CLAUDE.md, CONTRIBUTING.md and docs/decisions/README.md.

Documentation

Overview

Package termcast streams the screen of a chromedp page to a terminal that has graphics.

It uses the screencast of the Chrome DevTools Protocol to get the frames of the page, and the package rasterm to draw them. The terminal must support Kitty, iTerm2 or Sixel graphics. Start returns ErrNoGraphics when it does not.

The stream clears the terminal at each redraw and draws the latest frame. It uses no alternate screen and does not hide the cursor. A program that ends with log.Fatal skips its deferred calls, so the stream must never leave the terminal in a changed state. The browser sends a frame only when the page changes. The stream keeps the latest frame and draws it again only when it is new, so a page that stops changing stays on the screen.

The log lines of the program draw over the frames, and the next redraw erases them. Stream.LogWriter holds the lines while the stream runs and prints them when the stream stops, after a final frame. The stream does not work together with the protocol messages of a flag such as -v, because they also draw over the frames. Flags.Start returns ErrVerbose for that combination.

Every method of *Stream is safe on a nil pointer. A program can write s.Stop, s.Fatal and s.LogWriter whether the stream is on or off. See Flags and the example for the intended use.

Example

A program registers the flags, starts the browser, and starts the stream after the page has loaded. The stream is nil when the flag is off, and a nil stream is safe to use.

package main

import (
	"context"
	"flag"
	"log"
	"time"

	"github.com/chromedp/chromedp"
	"github.com/chromedp/termcast"
)

func main() {
	var tc termcast.Flags
	tc.Register(flag.CommandLine)
	verbose := flag.Bool("v", false, "print the protocol messages")
	flag.Parse()

	ctx, cancel := chromedp.NewContext(context.Background())
	defer cancel()

	// Load the page. This call starts the browser.
	if err := chromedp.Do(ctx, chromedp.Navigate("https://example.com")); err != nil {
		log.Fatal(err)
	}

	// Start the stream. It returns termcast.ErrVerbose for -v together with
	// -visible-on-terminal, and termcast.ErrNoGraphics for a terminal that has
	// no graphics.
	s, err := tc.Start(ctx, *verbose)
	if err != nil {
		log.Fatal(err)
	}
	// The log lines wait until the stream stops, so that they do not draw over
	// the frames.
	log.SetOutput(s.LogWriter())
	defer s.Stop()

	log.Println("the page is on the screen")
	if err := chromedp.Do(ctx, chromedp.Sleep(5*time.Second)); err != nil {
		// Fatal stops the stream, prints the log lines and exits.
		s.Fatal(err)
	}
}

Index

Examples

Constants

View Source
const (
	// DefaultFPS is the number of frames per second that the stream draws.
	DefaultFPS = 4
	// DefaultQuality is the JPEG quality that the screencast asks for.
	DefaultQuality = 80
	// DefaultWidth is the largest width of a frame in pixels.
	DefaultWidth = 1024
	// DefaultHeight is the largest height of a frame in pixels.
	DefaultHeight = 768
)

Default values of the options.

Variables

This section is empty.

Functions

func Available

func Available() bool

Available reports whether the terminal has graphics that Start can use with its default encoder. It reports what rasterm.Available reports.

Types

type Error

type Error string

Error is the type of the constant errors of this package.

const (
	// ErrNoGraphics is the error that [Start] returns when the terminal has no
	// graphics. rasterm detects Kitty, iTerm2 and Sixel from the environment
	// and from a query to the terminal. The variable TERM_GRAPHICS can force
	// the type to kitty, iterm or sixel, and the value none turns graphics
	// off.
	ErrNoGraphics Error = "" /* 131-byte string literal not displayed */
	// ErrVerbose is the error for the flag of the stream together with the
	// flag -v.
	ErrVerbose Error = "termcast: the stream on the terminal does not work with -v, because the protocol messages draw over the frames"
)

func (Error) Error

func (err Error) Error() string

Error satisfies the [error] interface.

type Flags

type Flags struct {
	// Enabled is the value of the flag -visible-on-terminal.
	Enabled bool
	// FPS is the value of the flag -terminal-fps.
	FPS float64
}

Flags holds the command line flags that a program uses to turn the stream on. Call Flags.Register before the program parses its flags, and Flags.Start after the browser has started.

func (*Flags) Register

func (f *Flags) Register(fs *flag.FlagSet)

Register adds the flags -visible-on-terminal and -terminal-fps to fs. A nil fs means flag.CommandLine.

func (*Flags) Start

func (f *Flags) Start(ctx context.Context, verbose bool, opts ...Option) (*Stream, error)

Start starts a stream when the flag -visible-on-terminal is on. It returns a nil stream and no error when the flag is off. A nil stream is safe to use. It returns ErrVerbose when the flag is on and verbose is true, because the protocol messages of -v can draw over the frames. Otherwise it returns the result of Start, with the frame rate of -terminal-fps.

type Option

type Option func(*config)

Option changes how Start makes a stream.

func WithElement added in v0.2.0

func WithElement[S chromedp.Selectable](sel S) Option

WithElement makes the stream draw only the element that sel selects, and not the whole page. The element is the first one that the selector matches, for example chromedp.CSS("#chart"). The stream looks the element up again at each frame, so an element that moves or changes its size stays in view.

The browser sends the viewport, so the stream shows the part of the element that is in the viewport. Scroll the element into view first, for example with chromedp.ScrollIntoView. While the page has no such element, the stream keeps what is on the screen.

func WithEncoder

func WithEncoder(enc rasterm.Encoder) Option

WithEncoder sets the encoder that draws a frame. The default encoder uses Kitty, iTerm2 or Sixel graphics, as rasterm detects them. Start asks the encoder that you give if it is available, and it does not ask rasterm.

func WithFPS

func WithFPS(fps float64) Option

WithFPS sets the number of frames per second that the stream draws at most. The default is DefaultFPS.

func WithLogOutput

func WithLogOutput(w io.Writer) Option

WithLogOutput sets the writer that receives the held log lines when the stream stops. The default is os.Stderr.

func WithMaxSize

func WithMaxSize(width, height int) Option

WithMaxSize sets the largest width and height of a frame in pixels. The browser scales a larger page down. The default is DefaultWidth by DefaultHeight.

func WithOutput

func WithOutput(w io.Writer) Option

WithOutput sets the writer that receives the frames. The default is os.Stdout.

func WithQuality

func WithQuality(quality int) Option

WithQuality sets the JPEG quality of the frames that the browser sends, from 0 to 100. The default is DefaultQuality.

type Stream

type Stream struct {
	// contains filtered or unexported fields
}

Stream draws the screen of a page in the terminal. Start makes it. Every method is safe on a nil pointer, and safe to call from several goroutines.

func Start

func Start(ctx context.Context, opts ...Option) (*Stream, error)

Start starts the screencast of the page of ctx and draws it in the terminal. The context is a chromedp context. Start starts the browser and opens the target when the context has none yet, as chromedp.Run does.

Start returns ErrNoGraphics when the encoder is not available. The stream ends when the context ends, and then it acts as Stream.Stop.

func (*Stream) Err

func (s *Stream) Err() error

Err returns the first error that the stream met while it ran, such as an error of the encoder or of the output. It returns nil on a nil stream.

func (*Stream) Fatal

func (s *Stream) Fatal(v ...any)

Fatal stops the stream, as Stream.Stop does, and then calls log.Fatal with v. The program ends with the exit status 1. On a nil stream, it only calls log.Fatal.

func (*Stream) LogWriter

func (s *Stream) LogWriter() io.Writer

LogWriter returns a writer for the log lines of the program, for example for log.SetOutput. While the stream runs, the writer holds the lines in memory. Stream.Stop prints them to the log output after the final frame. After Stream.Stop, the writer passes each line on at once. On a nil stream, it returns os.Stderr.

func (*Stream) Stop

func (s *Stream) Stop()

Stop stops the screencast and the goroutines of the stream, draws the final frame once, and then prints the held log lines. When no frame arrived, it takes one screenshot as the final frame. It is safe to call more than once and from several goroutines. A call that arrives while another one runs waits until that one is done. The stream also stops by itself when the context of Start ends. It does nothing on a nil stream.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL