Library Usage
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.
go get github.com/Gaurav-Gosain/tuios/pkg/tuiosThe 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.
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:
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. |
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. |
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. |
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:
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.
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:
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:
type PTY interface {
Width() int
Height() int
}
func NewForPTY(pty PTY, opts ...Option) *ModelThe 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 serves a Bubble Tea program to the browser. It is the library behind tuios-web. Use ServeWithProgram and build the program yourself:
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, use MiddlewareWithProgramHandler for the same reason: bubbletea.Middleware appends its options after yours.
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
tuiosbinary.pkg/tuiosgives you an in-process window manager. - No SSH or web helper.
tuios sshandtuios-webare 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.tapescripts and a dock config, not Go programs. The recipes on this page all compile against currentmain.