144 lines
4.3 KiB
Go
144 lines
4.3 KiB
Go
// Package tui реализует единственный экран stegterm — стеганография
|
||
// изображений и аудио/SSTV, без таб-бара: тут нечего переключать между
|
||
// собой, есть один режим работы, ветвящийся по типу загруженного
|
||
// файла (см. stegoKind в stegoview.go).
|
||
package tui
|
||
|
||
import (
|
||
"fmt"
|
||
"image"
|
||
|
||
tea "github.com/charmbracelet/bubbletea"
|
||
|
||
"github.com/r3g1tpr0cs/stegterm/internal/audio"
|
||
"github.com/r3g1tpr0cs/stegterm/internal/stego"
|
||
)
|
||
|
||
// --- Типы сообщений — в burterm они жили в общей корневой model.go
|
||
// (там таких вкладок было четырнадцать, у каждой свой набор), здесь
|
||
// вкладка одна, так что и сообщения естественно живут рядом с ней. ---
|
||
|
||
type stegoLoadedMsg struct {
|
||
img *image.RGBA
|
||
path string
|
||
format string
|
||
size int64
|
||
}
|
||
|
||
type audioLoadedMsg struct {
|
||
wav *audio.WAV
|
||
path string
|
||
size int64
|
||
}
|
||
|
||
type stegoResultMsg struct{ result *image.RGBA }
|
||
type stegoExtractedMsg struct{ data []byte }
|
||
type stegoSavedMsg struct{ path string }
|
||
type stegoErrMsg struct{ err error }
|
||
|
||
// Model — корневая модель приложения: тонкая обёртка над stegoModel,
|
||
// адаптирующая её под интерфейс tea.Model верхнего уровня (stegoModel
|
||
// сама по себе не реализует tea.Model — её Update возвращает stegoModel,
|
||
// не tea.Model, тот же паттерн "суб-модели", что был в burterm для
|
||
// каждой вкладки).
|
||
type Model struct {
|
||
stego stegoModel
|
||
}
|
||
|
||
// New создаёт начальное состояние.
|
||
func New() Model {
|
||
return Model{stego: newStegoView()}
|
||
}
|
||
|
||
func (m Model) Init() tea.Cmd {
|
||
return nil
|
||
}
|
||
|
||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||
switch msg := msg.(type) {
|
||
case tea.WindowSizeMsg:
|
||
m.stego.SetSize(msg.Width-2, msg.Height-2)
|
||
return m, nil
|
||
|
||
case tea.KeyMsg:
|
||
if msg.String() == "ctrl+c" {
|
||
return m, tea.Quit
|
||
}
|
||
|
||
case stegoLoadedMsg:
|
||
m.stego.kind = stegoKindImage
|
||
m.stego.original = msg.img
|
||
m.stego.wav = nil
|
||
m.stego.loadedPath = msg.path
|
||
m.stego.format = msg.format
|
||
m.stego.fileSize = msg.size
|
||
m.stego.result = nil
|
||
m.stego.kittyEscape = ""
|
||
m.stego.extractedText = ""
|
||
m.stego.extractedHex = ""
|
||
m.stego.errMsg = ""
|
||
m.stego.statusMsg = fmt.Sprintf("загружено: %s (%dx%d)", msg.path, msg.img.Bounds().Dx(), msg.img.Bounds().Dy())
|
||
return m, nil
|
||
|
||
case audioLoadedMsg:
|
||
m.stego.kind = stegoKindAudio
|
||
m.stego.wav = msg.wav
|
||
m.stego.original = nil
|
||
m.stego.loadedPath = msg.path
|
||
m.stego.fileSize = msg.size
|
||
m.stego.result = nil
|
||
m.stego.kittyEscape = ""
|
||
m.stego.extractedText = ""
|
||
m.stego.extractedHex = ""
|
||
m.stego.errMsg = ""
|
||
var dur float64
|
||
if msg.wav.SampleRate > 0 {
|
||
dur = float64(len(msg.wav.Samples)) / float64(msg.wav.SampleRate)
|
||
}
|
||
m.stego.statusMsg = fmt.Sprintf("загружено: %s (%d Гц, %.2fс) — ctrl+r считает результат", msg.path, msg.wav.SampleRate, dur)
|
||
return m, nil
|
||
|
||
case stegoResultMsg:
|
||
m.stego.result = msg.result
|
||
m.stego.extractedText = ""
|
||
m.stego.extractedHex = ""
|
||
pngData, err := stego.EncodePNG(msg.result)
|
||
if err != nil {
|
||
m.stego.errMsg = err.Error()
|
||
m.stego.kittyEscape = ""
|
||
return m, nil
|
||
}
|
||
m.stego.errMsg = ""
|
||
m.stego.kittyEscape = stego.KittyEscape(pngData)
|
||
bnd := msg.result.Bounds()
|
||
m.stego.statusMsg = fmt.Sprintf("готово: %dx%d (ctrl+v — показать/повторить)", bnd.Dx(), bnd.Dy())
|
||
return m, kittyPrintCmd(m.stego.kittyEscape)
|
||
|
||
case stegoExtractedMsg:
|
||
text, hexStr := formatExtracted(msg.data)
|
||
m.stego.extractedText = text
|
||
m.stego.extractedHex = hexStr
|
||
m.stego.result = nil
|
||
m.stego.kittyEscape = ""
|
||
m.stego.errMsg = ""
|
||
m.stego.statusMsg = fmt.Sprintf("извлечено %d байт", len(msg.data))
|
||
return m, nil
|
||
|
||
case stegoSavedMsg:
|
||
m.stego.errMsg = ""
|
||
m.stego.statusMsg = "сохранено в " + msg.path
|
||
return m, nil
|
||
|
||
case stegoErrMsg:
|
||
m.stego.errMsg = msg.err.Error()
|
||
return m, nil
|
||
}
|
||
|
||
var cmd tea.Cmd
|
||
m.stego, cmd = m.stego.Update(msg)
|
||
return m, cmd
|
||
}
|
||
|
||
func (m Model) View() string {
|
||
return m.stego.View()
|
||
}
|