# Library Usage

URL: https://tuios.dev/docs/library

> Embed TUIOS in your own Bubble Tea program with the pkg/tuios Go package, or serve it over SSH or in the browser.

The `pkg/tuios` package exposes the window manager as a Bubble Tea model. You can run it on its own, wrap it in your own model, or serve it over SSH or the browser.

```bash
go get github.com/Gaurav-Gosain/tuios/pkg/tuios
```

> The API is pre-1.0 and can change between releases. `Model` is a type alias for the internal `app.OS` type, so its exported fields and methods change with it. Pin a version in your `go.mod`.

## Run it

`tuios.New` returns a `*tuios.Model`, which implements `tea.Model`. You build the program yourself, so you keep control of program options and error handling. There is no `tuios.Run`.

```go
package main

import (
	"log"

	tea "charm.land/bubbletea/v2"
	"github.com/Gaurav-Gosain/tuios/pkg/tuios"
)

func main() {
	model := tuios.New()

	p := tea.NewProgram(model, tuios.ProgramOptions()...)
	if _, err := p.Run(); err != nil {
		log.Fatal(err)
	}
}
```

## Options

`New` takes functional options:

```go
model := tuios.New(
	tuios.WithTheme("dracula"),
	tuios.WithShowKeys(true),
	tuios.WithAnimations(false),
	tuios.WithWorkspaces(4),
	tuios.WithBorderStyle("thick"),
	tuios.WithScrollbackLines(50000),
)
```

TUIOS reads the user's config file first and then applies your options on top, the same way the `tuios` binary applies its command-line flags. An option left at its zero value keeps whatever the config file says.

| Option                     | Type                 | If not set       | Notes                                                                                                  |
| -------------------------- | -------------------- | ---------------- | ------------------------------------------------------------------------------------------------------ |
| `WithTheme`                | `string`             | config file      | A theme name, such as `dracula` or `nord`.                                                             |
| `WithShowKeys`             | `bool`               | config file      | Shows the [showkeys overlay](https://tuios.dev/docs/showkeys).                                         |
| `WithAnimations`           | `bool`               | config file      | `false` turns animations off. `true` cannot turn them on if the config turned them off.                |
| `WithASCIIOnly`            | `bool`               | config file      | ASCII instead of Nerd Font glyphs.                                                                     |
| `WithWorkspaces`           | `int`                | `9`              | Clamped to 1 to 9.                                                                                     |
| `WithBorderStyle`          | `string`             | config file      | See [`border_style`](https://tuios.dev/docs/configuration#border_style).                               |
| `WithDockbarPosition`      | `string`             | config file      | `bottom`, `top` or `hidden`.                                                                           |
| `WithHideWindowButtons`    | `bool`               | config file      | Hides minimize, maximize and close.                                                                    |
| `WithWindowButtonStyle`    | `string`             | config file      | `pill` or `dots` (macOS traffic lights). The built-in default is `dots`.                               |
| `WithWindowButtonPosition` | `string`             | config file      | `left` or `right`. The built-in default is `left`.                                                     |
| `WithScrollbackLines`      | `int`                | `10000`          | Clamped to 100 to 1,000,000. Always applied, so it replaces the config's `scrollback_lines`.           |
| `WithSize`                 | `int, int`           | detected         | Initial width and height in cells.                                                                     |
| `WithSSHMode`              | `bool`               | `false`          | Marks the model as an SSH client. See [Serve over SSH](https://tuios.dev/docs/library#serve-over-ssh). |
| `WithUserConfig`           | `*config.UserConfig` | loaded from disk | Use this config instead of reading the user's file.                                                    |

Out-of-range numbers are clamped, not rejected. `WithWorkspaces(50)` gives 9 and `WithScrollbackLines(10)` gives 100. `DefaultOptions()` returns the starting values as an `Options` struct.

## Use your own config

`tuios.Config` exposes three config functions, so you do not need to import an internal package:

```go
cfg, err := tuios.Config.LoadUserConfig()
if err != nil {
	cfg = tuios.Config.DefaultConfig()
}
cfg.Keybindings.LeaderKey = "ctrl+a"

model := tuios.New(tuios.WithUserConfig(cfg))
```

`tuios.Config.GetConfigPath()` returns the path of the config file and an error.

## Wrap it in your own model

Delegate to the TUIOS model and handle your own messages first. In Bubble Tea v2, `View` returns a `tea.View`.

```go
type app struct {
	wm *tuios.Model
}

func (a *app) Init() tea.Cmd {
	return a.wm.Init()
}

func (a *app) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	if _, ok := msg.(pingMsg); ok {
		// Handle your own message and stop here.
		return a, nil
	}
	updated, cmd := a.wm.Update(msg)
	a.wm = updated.(*tuios.Model)
	return a, cmd
}

func (a *app) View() tea.View {
	return a.wm.View()
}
```

To check the mode, read the `Mode` field. The package exports `WindowManagementMode`, which TUIOS starts in, and `TerminalMode`:

```go
if model.Mode == tuios.TerminalMode {
	// keys go to the focused pane
}
```

## Program options

`ProgramOptions()` returns the Bubble Tea options every TUIOS client runs with:

- the frame rate cap from the config (`max_fps`)
- no signal handler, so your program owns the process signals
- `tea.WithFilter(tuios.FilterMouseMotion)`

Pass them through instead of writing your own, so you get later additions.

`FilterMouseMotion` drops mouse motion that nothing on screen reacts to. Motion still passes when something needs it, for example during a drag or resize, over a link, the dock or the sidebar, and when the focused pane runs an application that tracks the mouse. It is exported for callers that build their own option list.

> **The last filter wins**
>
> Bubble Tea keeps only one `tea.WithFilter`. SSH and web serving libraries add their own filter, so put their options first and `tuios.ProgramOptions()` last. Otherwise the TUIOS filter is replaced and every mouse motion event reaches the model.

## Size from a PTY

`NewForPTY` takes the initial size from anything with `Width()` and `Height()` methods:

```go
type PTY interface {
	Width() int
	Height() int
}

func NewForPTY(pty PTY, opts ...Option) *Model
```

The interface needs methods. A struct with `Width` and `Height` fields, such as `sip.Pty`, does not satisfy it. For those, use `New` with `WithSize`, as the recipes below do.

## Serve in the browser

[sip](https://github.com/Gaurav-Gosain/sip) serves a Bubble Tea program to the browser. It is the library behind `tuios-web`. Use `ServeWithProgram` and build the program yourself:

```go
package main

import (
	"context"
	"log"

	tea "charm.land/bubbletea/v2"
	"github.com/Gaurav-Gosain/sip"
	"github.com/Gaurav-Gosain/tuios/pkg/tuios"
)

func main() {
	server := sip.NewServer(sip.DefaultConfig())

	err := server.ServeWithProgram(context.Background(), func(sess sip.Session) *tea.Program {
		pty := sess.Pty()
		model := tuios.New(
			tuios.WithSize(pty.Width, pty.Height),
			tuios.WithTheme("dracula"),
		)
		// sip's options first, then tuios's, so the tuios filter wins.
		opts := append(sip.MakeOptions(sess), tuios.ProgramOptions()...)
		return tea.NewProgram(model, opts...)
	})
	if err != nil {
		log.Fatal(err)
	}
}
```

Do not use sip's `Serve` here. It appends its own options after the ones you return, so its filter replaces the TUIOS one. A model served this way counts as a local client: there is no option that marks it as a browser.

## Serve over SSH

With [Wish](https://github.com/charmbracelet/wish), use `MiddlewareWithProgramHandler` for the same reason: `bubbletea.Middleware` appends its options after yours.

```go
package main

import (
	"log"

	tea "charm.land/bubbletea/v2"
	"charm.land/ssh"
	"charm.land/wish/v2"
	"charm.land/wish/v2/bubbletea"
	"github.com/Gaurav-Gosain/tuios/pkg/tuios"
)

func main() {
	s, err := wish.NewServer(
		wish.WithAddress(":2222"),
		wish.WithHostKeyPath(".ssh/tuios_ed25519"),
		wish.WithMiddleware(
			bubbletea.MiddlewareWithProgramHandler(func(sess ssh.Session) *tea.Program {
				pty, _, _ := sess.Pty()
				model := tuios.New(
					tuios.WithSize(pty.Window.Width, pty.Window.Height),
					tuios.WithSSHMode(true),
				)
				// wish's options first, then tuios's, so the tuios filter wins.
				opts := append(bubbletea.MakeOptions(sess), tuios.ProgramOptions()...)
				return tea.NewProgram(model, opts...)
			}),
		),
	)
	if err != nil {
		log.Fatal(err)
	}
	log.Fatal(s.ListenAndServe())
}
```

`WithSSHMode(true)` makes the model an SSH client. The settings page does not write the server's config file, and desktop actions do not run on the server. It does not give the model the SSH session, a graphics channel or the client terminal's capabilities, so images are not forwarded to the client.

## Limitations

- **No daemon.** Sessions, detach and reattach, and the control protocol are internal and only reachable through the `tuios` binary. `pkg/tuios` gives you an in-process window manager.
- **No SSH or web helper.** `tuios ssh` and `tuios-web` are built from internal packages. Use the recipes above to serve the model yourself.
- **Some settings are process-wide.** Theme, border style, dockbar position, window buttons, ASCII mode, scrollback and animations are written to package-level state in the config package. Two models in one process share them, and the last one built wins. This matters when you serve several sessions from one process.
- **No Go examples in the repository.** The `examples/` directory holds `.tape` scripts and a dock config, not Go programs. The recipes on this page all compile against current `main`.

## Related

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/docs/library)*
