Skip to content

Commit

Permalink
feat: Add support for non-color terminals
Browse files Browse the repository at this point in the history
Closes #35
  • Loading branch information
gabe565 committed Nov 24, 2023
1 parent b8e897e commit 5a9b1b9
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 25 deletions.
3 changes: 2 additions & 1 deletion cmd/play/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/gabe565/ascii-movie/internal/config"
"github.com/gabe565/ascii-movie/internal/movie"
"github.com/muesli/termenv"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -38,7 +39,7 @@ func run(cmd *cobra.Command, args []string) (err error) {
return err
}

program := tea.NewProgram(movie.NewPlayer(&m, nil))
program := tea.NewProgram(movie.NewPlayer(&m, nil, termenv.ColorProfile()))
if _, err := program.Run(); err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module github.com/gabe565/ascii-movie

go 1.21.4

replace github.com/charmbracelet/lipgloss => github.com/gabe565/lipgloss v0.0.0-20231124201931-3d7efac1ed1b

require (
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.3-0.20231107170225-a6f07b8ba643
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ github.com/charmbracelet/bubbletea v0.24.3-0.20231107170225-a6f07b8ba643 h1:YvPB
github.com/charmbracelet/bubbletea v0.24.3-0.20231107170225-a6f07b8ba643/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/keygen v0.5.0 h1:XY0fsoYiCSM9axkrU+2ziE6u6YjJulo/b9Dghnw6MZc=
github.com/charmbracelet/keygen v0.5.0/go.mod h1:DfvCgLHxZ9rJxdK0DGw3C/LkV4SgdGbnliHcObV3L+8=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/charmbracelet/log v0.2.5 h1:1yVvyKCKVV639RR4LIq1iy1Cs1AKxuNO+Hx2LJtk7Wc=
github.com/charmbracelet/log v0.2.5/go.mod h1:nQGK8tvc4pS9cvVEH/pWJiZ50eUq1aoXUOjGpXvdD0k=
github.com/charmbracelet/ssh v0.0.0-20230822194956-1a051f898e09 h1:ZDIQmTtohv0S/AAYE//w8mYTxCzqphhF1+4ACPDMiLU=
Expand All @@ -30,6 +28,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabe565/lipgloss v0.0.0-20231124201931-3d7efac1ed1b h1:BZ+kR2CvKQxBWIJ0SAWx4O5QvhnnywR4RLjjYFGYyZw=
github.com/gabe565/lipgloss v0.0.0-20231124201931-3d7efac1ed1b/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down
28 changes: 20 additions & 8 deletions internal/movie/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,25 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/gabe565/ascii-movie/internal/log_hooks"
"github.com/muesli/termenv"
log "github.com/sirupsen/logrus"
)

func NewPlayer(m *Movie, logger *log.Entry) Player {
func NewPlayer(m *Movie, logger *log.Entry, profile termenv.Profile) Player {
player := Player{
movie: m,
profile: profile,
speed: 1,
selectedOption: 3,
activeOption: 4,
optionsStyle: &optionsStyle,
activeStyle: &activeStyle,
optionViewStale: true,
}
if profile == termenv.Ascii {
player.optionsStyle = &optionsStyleAnsi
player.activeStyle = &activeStyleAnsi
}
player.play()
if logger != nil {
player.durationHook = log_hooks.NewDuration()
Expand All @@ -28,7 +36,7 @@ func NewPlayer(m *Movie, logger *log.Entry) Player {

player.keymap = newKeymap()
helpModel := help.New()
player.helpViewCache = helpModel.ShortHelpView([]key.Binding{
player.helpViewCache = RenderHelpWithProfile(profile, helpModel, []key.Binding{
player.keymap.quit,
player.keymap.left,
player.keymap.right,
Expand All @@ -43,13 +51,16 @@ type Player struct {
frame int
log *log.Entry
durationHook log_hooks.Duration
profile termenv.Profile

speed float64
playCtx context.Context
playCancel context.CancelFunc

selectedOption int
activeOption int
optionsStyle *lipgloss.Style
activeStyle *lipgloss.Style
optionViewCache string
optionViewStale bool

Expand Down Expand Up @@ -184,29 +195,30 @@ func (p Player) View() string {
p.optionViewCache = p.OptionsView()
}

return appStyle.Render(lipgloss.JoinVertical(
return appStyle.RenderWithProfile(p.profile, lipgloss.JoinVertical(
lipgloss.Center,
p.movie.screenStyle.Render(p.movie.Frames[p.frame].Data),
progressStyle.Render(p.movie.Frames[p.frame].Progress),
p.movie.screenStyle.RenderWithProfile(p.profile, p.movie.Frames[p.frame].Data),
progressStyle.RenderWithProfile(p.profile, p.movie.Frames[p.frame].Progress),
p.optionViewCache,
p.helpViewCache,
))
}

func (p *Player) OptionsView() string {
p.optionViewStale = false

options := make([]string, 0, len(playerOptions))
for i, option := range playerOptions {
if option == OptionPause && !p.isPlaying() {
option = OptionPlay
}
var rendered string
if i == p.selectedOption {
rendered = selectedStyle.Render(string(option))
rendered = selectedStyle.RenderWithProfile(p.profile, string(option))
} else if i == p.activeOption {
rendered = activeStyle.Render(string(option))
rendered = p.activeStyle.RenderWithProfile(p.profile, string(option))
} else {
rendered = optionsStyle.Render(string(option))
rendered = p.optionsStyle.RenderWithProfile(p.profile, string(option))
}
options = append(options, rendered)
}
Expand Down
56 changes: 56 additions & 0 deletions internal/movie/player_help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package movie

import (
"strings"

"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/termenv"
)

func RenderHelpWithProfile(p termenv.Profile, m help.Model, bindings []key.Binding) string {
if len(bindings) == 0 {
return ""
}

var b strings.Builder
var totalWidth int
separator := m.Styles.ShortSeparator.Inline(true).RenderWithProfile(p, m.ShortSeparator)

for i, kb := range bindings {
if !kb.Enabled() {
continue
}

var sep string
if totalWidth > 0 && i < len(bindings) {
sep = separator
}

str := sep +
m.Styles.ShortKey.Inline(true).RenderWithProfile(p, kb.Help().Key) + " " +
m.Styles.ShortDesc.Inline(true).RenderWithProfile(p, kb.Help().Desc)

w := lipgloss.Width(str)

// If adding this help item would go over the available width, stop
// drawing.
if m.Width > 0 && totalWidth+w > m.Width {
// Although if there's room for an ellipsis, print that.
tail := " " + m.Styles.Ellipsis.Inline(true).RenderWithProfile(p, m.Ellipsis)
tailWidth := lipgloss.Width(tail)

if totalWidth+tailWidth < m.Width {
b.WriteString(tail)
}

break
}

totalWidth += w
b.WriteString(str)
}

return b.String()
}
8 changes: 8 additions & 0 deletions internal/movie/player_styles.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,21 @@ var (
BorderForeground(optionsColor).
Background(optionsColor)

optionsStyleAnsi = optionsStyle.Copy().
Padding(0, 2).
Margin(1).
Border(lipgloss.InnerHalfBlockBorder(), false)

activeColor = lipgloss.AdaptiveColor{Light: "8", Dark: "12"}
activeStyle = optionsStyle.Copy().
Background(activeColor).
BorderForeground(activeColor).
Foreground(lipgloss.AdaptiveColor{Light: "15"}).
Bold(true)

activeStyleAnsi = activeStyle.Copy().
BorderStyle(lipgloss.DoubleBorder())

selectedColor = lipgloss.AdaptiveColor{Light: "12", Dark: "4"}
selectedStyle = optionsStyle.Copy().
Background(selectedColor).
Expand Down
5 changes: 3 additions & 2 deletions internal/movie/player_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ package movie
import (
"testing"

"github.com/muesli/termenv"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestNewPlayer(t *testing.T) {
t.Run("simple", func(t *testing.T) {
movie := NewMovie()
player := NewPlayer(&movie, log.WithField("test", t.Name()))
player := NewPlayer(&movie, log.WithField("test", t.Name()), termenv.ColorProfile())
assert.Equal(t, &movie, player.movie)
assert.NotNil(t, player.log)
assert.NotEmpty(t, player.durationHook)
})

t.Run("no logger", func(t *testing.T) {
movie := NewMovie()
player := NewPlayer(&movie, nil)
player := NewPlayer(&movie, nil, termenv.ColorProfile())
assert.Equal(t, &movie, player.movie)
assert.Nil(t, player.log)
assert.Empty(t, player.durationHook)
Expand Down
6 changes: 5 additions & 1 deletion internal/server/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/charmbracelet/wish"
"github.com/charmbracelet/wish/bubbletea"
"github.com/gabe565/ascii-movie/internal/movie"
"github.com/gabe565/ascii-movie/internal/util"
"github.com/muesli/termenv"
log "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
Expand Down Expand Up @@ -112,7 +113,10 @@ func (s *SSHServer) Handler(m *movie.Movie) bubbletea.ProgramHandler {
"user": session.User(),
})

player := movie.NewPlayer(m, logger)
pty, _, _ := session.Pty()
profile := util.Profile(pty.Term)

player := movie.NewPlayer(m, logger, profile)
program := tea.NewProgram(
player,
tea.WithInput(session),
Expand Down
17 changes: 10 additions & 7 deletions internal/server/telnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/gabe565/ascii-movie/internal/movie"
"github.com/gabe565/ascii-movie/internal/server/telnet"
"github.com/gabe565/ascii-movie/internal/util"
flag "github.com/spf13/pflag"
)

Expand Down Expand Up @@ -108,7 +109,15 @@ func (s *TelnetServer) Handler(ctx context.Context, conn net.Conn, m *movie.Movi
ctx, cancel := context.WithCancel(ctx)
defer cancel()

player := movie.NewPlayer(m, logger)
termCh := make(chan string)
go func() {
// Proxy input to program
_ = telnet.Proxy(conn, inW, termCh)
cancel()
}()
profile := util.Profile(<-termCh)

player := movie.NewPlayer(m, logger, profile)
program := tea.NewProgram(
player,
tea.WithInput(inR),
Expand Down Expand Up @@ -140,12 +149,6 @@ func (s *TelnetServer) Handler(ctx context.Context, conn net.Conn, m *movie.Movi
_, _ = io.Copy(io.Discard, outR)
}()

go func() {
// Proxy input to program
_ = telnet.Proxy(conn, inW)
cancel()
}()

if _, err := program.Run(); err != nil && !errors.Is(err, tea.ErrProgramKilled) {
logger.WithError(err).Error("Stream failed")
}
Expand Down
44 changes: 40 additions & 4 deletions internal/server/telnet/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import (
log "github.com/sirupsen/logrus"
)

func Proxy(conn net.Conn, proxy io.Writer) error {
func Proxy(conn net.Conn, proxy io.Writer, termCh chan string) error {
reader := bufio.NewReaderSize(conn, 64)
var wroteTelnetCommands bool
var wroteTermType bool

// Gets Telnet to send option negotiation commands if explicit port was given.
// Also clears the line in case the client isn't Telnet
Expand All @@ -22,6 +23,15 @@ func Proxy(conn net.Conn, proxy io.Writer) error {
return err
}

go func() {
time.Sleep(250 * time.Millisecond)
if !wroteTermType {
wroteTermType = true
log.Trace("Did not get terminal type in time")
close(termCh)
}
}()

for {
b, err := reader.ReadByte()
if err != nil {
Expand All @@ -33,10 +43,11 @@ func Proxy(conn net.Conn, proxy io.Writer) error {
case Iac:
// https://ibm.com/docs/zos/2.5.0?topic=problems-telnet-commands-options
if conn != nil && !wroteTelnetCommands {
log.Trace("Writing Telnet commands")
log.Trace("Configuring Telnet")
if _, err := Write(conn,
Iac, Will, Echo,
Iac, Will, SuppressGoAhead,
Iac, Do, TerminalType,
); err != nil {
log.WithError(err).Error("Failed to write Telnet commands")
}
Expand All @@ -53,14 +64,39 @@ func Proxy(conn net.Conn, proxy io.Writer) error {
if err := conn.SetReadDeadline(time.Now().Add(250 * time.Millisecond)); err != nil {
return err
}
_, err := reader.ReadBytes(byte(Se))
command, err := reader.ReadBytes(byte(Se))
if err != nil && !errors.Is(err, os.ErrDeadlineExceeded) {
return err
}
if err := conn.SetReadDeadline(time.Time{}); err != nil {
return err
}
case Will, Wont, Do, Dont:

if len(command) != 0 {
switch Operator(command[0]) {
case TerminalType:
if len(command) > 5 && !wroteTermType {
wroteTermType = true
term := string(command[2 : len(command)-2])
log.Trace("Got terminal type")
termCh <- term
close(termCh)
}
}
}
case Will:
if b, err = reader.ReadByte(); err != nil {
return err
}

switch Operator(b) {
case TerminalType:
log.Trace("Requesting terminal type")
if _, err := Write(conn, Iac, Subnegotiation, TerminalType, 1, Iac, Se); err != nil {
return err
}
}
case Wont, Do, Dont:
if _, err := reader.Discard(1); err != nil {
return err
}
Expand Down
Loading

0 comments on commit 5a9b1b9

Please sign in to comment.