mirror of https://github.com/jetkvm/kvm.git
Update serial console part
This commit is contained in:
parent
7c09ac3c08
commit
0630a7bcb1
64
serial.go
64
serial.go
|
|
@ -255,20 +255,9 @@ func setDCRestoreState(state int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func mountSerialButtons() error {
|
||||
_ = port.SetMode(defaultMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmountSerialButtons() error {
|
||||
_ = reopenSerialPort()
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendCustomCommand(command string) error {
|
||||
scopedLogger := serialLogger.With().Str("service", "custom_buttons_tx").Logger()
|
||||
scopedLogger.Info().Str("Command", command).Msg("Sending custom command.")
|
||||
scopedLogger.Info().Msgf("Sending custom command: %q", command)
|
||||
scopedLogger.Debug().Msgf("Sending custom command: %q", command)
|
||||
if serialMux == nil {
|
||||
return fmt.Errorf("serial mux not initialized")
|
||||
}
|
||||
|
|
@ -325,7 +314,7 @@ type SerialSettings struct {
|
|||
HideSerialSettings bool `json:"hideSerialSettings"` // Whether to hide the serial settings in the UI
|
||||
EnableEcho bool `json:"enableEcho"` // Whether to echo received characters back to the sender
|
||||
NormalizeMode string `json:"normalizeMode"` // Normalization mode: "carret", "names", "hex"
|
||||
NormalizeLineEnd string `json:"normalizeLineEnd"` // Line ending normalization: "keep", "lf", "cr", "crlf"
|
||||
NormalizeLineEnd string `json:"normalizeLineEnd"` // Line ending normalization: "keep", "lf", "cr", "crlf", "lfcr"
|
||||
TabRender string `json:"tabRender"` // How to render tabs: "spaces", "arrow", "pipe"
|
||||
PreserveANSI bool `json:"preserveANSI"` // Whether to preserve ANSI escape codes
|
||||
ShowNLTag bool `json:"showNLTag"` // Whether to show a special tag for new lines
|
||||
|
|
@ -358,7 +347,7 @@ func getSerialSettings() (SerialSettings, error) {
|
|||
|
||||
file, err := os.Open(serialSettingsPath)
|
||||
if err != nil {
|
||||
logger.Debug().Msg("SerialButtons config file doesn't exist, using default")
|
||||
logger.Info().Msg("SerialButtons config file doesn't exist, using default")
|
||||
return serialConfig, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
|
@ -412,7 +401,7 @@ func getSerialSettings() (SerialSettings, error) {
|
|||
|
||||
var normalizeMode NormalizeMode
|
||||
switch serialConfig.NormalizeMode {
|
||||
case "carret":
|
||||
case "caret":
|
||||
normalizeMode = ModeCaret
|
||||
case "names":
|
||||
normalizeMode = ModeNames
|
||||
|
|
@ -422,25 +411,25 @@ func getSerialSettings() (SerialSettings, error) {
|
|||
normalizeMode = ModeNames
|
||||
}
|
||||
|
||||
var crlfMode CRLFMode
|
||||
var crlfMode LineEndingMode
|
||||
switch serialConfig.NormalizeLineEnd {
|
||||
case "keep":
|
||||
crlfMode = CRLFAsIs
|
||||
crlfMode = LineEnding_AsIs
|
||||
case "lf":
|
||||
crlfMode = CRLF_LF
|
||||
crlfMode = LineEnding_LF
|
||||
case "cr":
|
||||
crlfMode = CRLF_CR
|
||||
crlfMode = LineEnding_CR
|
||||
case "crlf":
|
||||
crlfMode = CRLF_CRLF
|
||||
crlfMode = LineEnding_CRLF
|
||||
case "lfcr":
|
||||
crlfMode = CRLF_LFCR
|
||||
crlfMode = LineEnding_LFCR
|
||||
default:
|
||||
crlfMode = CRLFAsIs
|
||||
crlfMode = LineEnding_AsIs
|
||||
}
|
||||
|
||||
if consoleBroker != nil {
|
||||
norm := NormOptions{
|
||||
Mode: normalizeMode, CRLF: crlfMode, TabRender: serialConfig.TabRender, PreserveANSI: serialConfig.PreserveANSI, ShowNLTag: serialConfig.ShowNLTag,
|
||||
norm := NormalizationOptions{
|
||||
Mode: normalizeMode, LineEnding: crlfMode, TabRender: serialConfig.TabRender, PreserveANSI: serialConfig.PreserveANSI, ShowNLTag: serialConfig.ShowNLTag,
|
||||
}
|
||||
consoleBroker.SetNormOptions(norm)
|
||||
}
|
||||
|
|
@ -507,7 +496,7 @@ func setSerialSettings(newSettings SerialSettings) error {
|
|||
|
||||
var normalizeMode NormalizeMode
|
||||
switch serialConfig.NormalizeMode {
|
||||
case "carret":
|
||||
case "caret":
|
||||
normalizeMode = ModeCaret
|
||||
case "names":
|
||||
normalizeMode = ModeNames
|
||||
|
|
@ -517,25 +506,25 @@ func setSerialSettings(newSettings SerialSettings) error {
|
|||
normalizeMode = ModeNames
|
||||
}
|
||||
|
||||
var crlfMode CRLFMode
|
||||
var crlfMode LineEndingMode
|
||||
switch serialConfig.NormalizeLineEnd {
|
||||
case "keep":
|
||||
crlfMode = CRLFAsIs
|
||||
crlfMode = LineEnding_AsIs
|
||||
case "lf":
|
||||
crlfMode = CRLF_LF
|
||||
crlfMode = LineEnding_LF
|
||||
case "cr":
|
||||
crlfMode = CRLF_CR
|
||||
crlfMode = LineEnding_CR
|
||||
case "crlf":
|
||||
crlfMode = CRLF_CRLF
|
||||
crlfMode = LineEnding_CRLF
|
||||
case "lfcr":
|
||||
crlfMode = CRLF_LFCR
|
||||
crlfMode = LineEnding_LFCR
|
||||
default:
|
||||
crlfMode = CRLFAsIs
|
||||
crlfMode = LineEnding_AsIs
|
||||
}
|
||||
|
||||
if consoleBroker != nil {
|
||||
norm := NormOptions{
|
||||
Mode: normalizeMode, CRLF: crlfMode, TabRender: serialConfig.TabRender, PreserveANSI: serialConfig.PreserveANSI, ShowNLTag: serialConfig.ShowNLTag,
|
||||
norm := NormalizationOptions{
|
||||
Mode: normalizeMode, LineEnding: crlfMode, TabRender: serialConfig.TabRender, PreserveANSI: serialConfig.PreserveANSI, ShowNLTag: serialConfig.ShowNLTag,
|
||||
}
|
||||
consoleBroker.SetNormOptions(norm)
|
||||
}
|
||||
|
|
@ -575,8 +564,8 @@ func reopenSerialPort() error {
|
|||
}
|
||||
|
||||
// new broker (no sink yet—set it in handleSerialChannel.OnOpen)
|
||||
norm := NormOptions{
|
||||
Mode: ModeNames, CRLF: CRLF_LF, TabRender: "", PreserveANSI: true,
|
||||
norm := NormalizationOptions{
|
||||
Mode: ModeNames, LineEnding: LineEnding_LF, TabRender: "", PreserveANSI: true,
|
||||
}
|
||||
if consoleBroker != nil {
|
||||
consoleBroker.Close()
|
||||
|
|
@ -612,8 +601,7 @@ func handleSerialChannel(dataChannel *webrtc.DataChannel) {
|
|||
|
||||
dataChannel.OnMessage(func(msg webrtc.DataChannelMessage) {
|
||||
|
||||
scopedLogger.Info().Bytes("Data:", msg.Data).Msg("Sending data to serial mux")
|
||||
scopedLogger.Info().Msgf("Sending data to serial mux: %q", msg.Data)
|
||||
scopedLogger.Trace().Bytes("Data:", msg.Data).Msg("Sending data to serial mux")
|
||||
if serialMux == nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,25 +31,25 @@ const (
|
|||
ModeHex // \x1B
|
||||
)
|
||||
|
||||
type CRLFMode int
|
||||
type LineEndingMode int
|
||||
|
||||
const (
|
||||
CRLFAsIs CRLFMode = iota
|
||||
CRLF_LF
|
||||
CRLF_CR
|
||||
CRLF_CRLF
|
||||
CRLF_LFCR
|
||||
LineEnding_AsIs LineEndingMode = iota
|
||||
LineEnding_LF
|
||||
LineEnding_CR
|
||||
LineEnding_CRLF
|
||||
LineEnding_LFCR
|
||||
)
|
||||
|
||||
type NormOptions struct {
|
||||
type NormalizationOptions struct {
|
||||
Mode NormalizeMode
|
||||
CRLF CRLFMode
|
||||
LineEnding LineEndingMode
|
||||
TabRender string // e.g. " " or "" to keep '\t'
|
||||
PreserveANSI bool
|
||||
ShowNLTag bool // <- NEW: also print a visible tag for CR/LF
|
||||
ShowNLTag bool // print a visible tag for CR/LF like <CR>, <LF>, <CRLF>
|
||||
}
|
||||
|
||||
func normalize(in []byte, opt NormOptions) string {
|
||||
func normalize(in []byte, opt NormalizationOptions) string {
|
||||
var out strings.Builder
|
||||
esc := byte(0x1B)
|
||||
for i := 0; i < len(in); {
|
||||
|
|
@ -113,8 +113,8 @@ func normalize(in []byte, opt NormOptions) string {
|
|||
}
|
||||
|
||||
// now emit the actual newline(s) per the normalization mode
|
||||
switch opt.CRLF {
|
||||
case CRLFAsIs:
|
||||
switch opt.LineEnding {
|
||||
case LineEnding_AsIs:
|
||||
if isPair {
|
||||
out.WriteByte(b)
|
||||
out.WriteByte(in[i+1])
|
||||
|
|
@ -123,28 +123,28 @@ func normalize(in []byte, opt NormOptions) string {
|
|||
out.WriteByte(b)
|
||||
i++
|
||||
}
|
||||
case CRLF_LF:
|
||||
case LineEnding_LF:
|
||||
if isPair {
|
||||
i += 2
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
out.WriteByte('\n')
|
||||
case CRLF_CR:
|
||||
case LineEnding_CR:
|
||||
if isPair {
|
||||
i += 2
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
out.WriteByte('\r')
|
||||
case CRLF_CRLF:
|
||||
case LineEnding_CRLF:
|
||||
if isPair {
|
||||
i += 2
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
out.WriteString("\r\n") // (fixed to actually write CRLF)
|
||||
case CRLF_LFCR:
|
||||
out.WriteString("\r\n")
|
||||
case LineEnding_LFCR:
|
||||
if isPair {
|
||||
i += 2
|
||||
} else {
|
||||
|
|
@ -238,14 +238,14 @@ type ConsoleBroker struct {
|
|||
quietAfter time.Duration
|
||||
|
||||
// normalization
|
||||
norm NormOptions
|
||||
norm NormalizationOptions
|
||||
|
||||
// labels
|
||||
labelRX string
|
||||
labelTX string
|
||||
}
|
||||
|
||||
func NewConsoleBroker(s Sink, norm NormOptions) *ConsoleBroker {
|
||||
func NewConsoleBroker(s Sink, norm NormalizationOptions) *ConsoleBroker {
|
||||
return &ConsoleBroker{
|
||||
sink: s,
|
||||
in: make(chan consoleEvent, 256),
|
||||
|
|
@ -267,7 +267,7 @@ func NewConsoleBroker(s Sink, norm NormOptions) *ConsoleBroker {
|
|||
func (b *ConsoleBroker) Start() { go b.loop() }
|
||||
func (b *ConsoleBroker) Close() { close(b.done) }
|
||||
func (b *ConsoleBroker) SetSink(s Sink) { b.sink = s }
|
||||
func (b *ConsoleBroker) SetNormOptions(norm NormOptions) { b.norm = norm }
|
||||
func (b *ConsoleBroker) SetNormOptions(norm NormalizationOptions) { b.norm = norm }
|
||||
func (b *ConsoleBroker) SetTerminalPaused(v bool) {
|
||||
if b == nil {
|
||||
return
|
||||
|
|
@ -293,23 +293,23 @@ func (b *ConsoleBroker) loop() {
|
|||
|
||||
case v := <-b.pauseCh:
|
||||
// apply pause state
|
||||
was := b.terminalPaused
|
||||
wasPaused := b.terminalPaused
|
||||
b.terminalPaused = v
|
||||
if was && !v {
|
||||
if wasPaused && !v {
|
||||
// we just unpaused: flush buffered output in order
|
||||
scopedLogger.Info().Msg("Terminal unpaused; flushing buffered output")
|
||||
scopedLogger.Trace().Msg("Terminal unpaused; flushing buffered output")
|
||||
b.flushBuffer()
|
||||
} else if !was && v {
|
||||
scopedLogger.Info().Msg("Terminal paused; buffering output")
|
||||
} else if !wasPaused && v {
|
||||
scopedLogger.Trace().Msg("Terminal paused; buffering output")
|
||||
}
|
||||
|
||||
case ev := <-b.in:
|
||||
switch ev.kind {
|
||||
case evRX:
|
||||
scopedLogger.Info().Msg("Processing RX data from serial port")
|
||||
scopedLogger.Trace().Msg("Processing RX data from serial port")
|
||||
b.handleRX(ev.data)
|
||||
case evTX:
|
||||
scopedLogger.Info().Msg("Processing TX echo request")
|
||||
scopedLogger.Trace().Msg("Processing TX echo request")
|
||||
b.handleTX(ev.data)
|
||||
}
|
||||
|
||||
|
|
@ -367,10 +367,10 @@ func (b *ConsoleBroker) handleRX(data []byte) {
|
|||
return
|
||||
}
|
||||
|
||||
scopedLogger.Info().Msg("Emitting RX data to sink (with per-line prefixes)")
|
||||
scopedLogger.Trace().Msg("Emitting RX data to sink (with per-line prefixes)")
|
||||
|
||||
// Prefix every line, regardless of how the EOLs look
|
||||
lines := splitAfterAnyEOL(text, b.norm.CRLF)
|
||||
lines := splitAfterAnyEOL(text, b.norm.LineEnding)
|
||||
|
||||
// Start from the broker's current RX line state
|
||||
atLineEnd := b.rxAtLineEnd
|
||||
|
|
@ -389,7 +389,7 @@ func (b *ConsoleBroker) handleRX(data []byte) {
|
|||
}
|
||||
|
||||
// Update line-end state based on this piece
|
||||
atLineEnd = endsWithEOL(line, b.norm.CRLF)
|
||||
atLineEnd = endsWithEOL(line, b.norm.LineEnding)
|
||||
}
|
||||
|
||||
// Persist state for next RX chunk
|
||||
|
|
@ -407,11 +407,11 @@ func (b *ConsoleBroker) handleTX(data []byte) {
|
|||
return
|
||||
}
|
||||
if b.rxAtLineEnd && b.pendingTX == nil {
|
||||
scopedLogger.Info().Msg("Emitting TX data to sink immediately")
|
||||
scopedLogger.Trace().Msg("Emitting TX data to sink immediately")
|
||||
b.emitTX(data)
|
||||
return
|
||||
}
|
||||
scopedLogger.Info().Msg("Queuing TX data to emit after RX line completion or quiet period")
|
||||
scopedLogger.Trace().Msg("Queuing TX data to emit after RX line completion or quiet period")
|
||||
b.pendingTX = &consoleEvent{kind: evTX, data: append([]byte(nil), data...)}
|
||||
b.startQuietTimer()
|
||||
}
|
||||
|
|
@ -430,12 +430,12 @@ func (b *ConsoleBroker) emitTX(data []byte) {
|
|||
// Check if we’re in the middle of a TX line
|
||||
if !b.txLineActive {
|
||||
// Start new TX line with prefix
|
||||
scopedLogger.Info().Msg("Emitting TX data to sink with prefix")
|
||||
scopedLogger.Trace().Msg("Emitting TX data to sink with prefix")
|
||||
b.emitToTerminal(fmt.Sprintf("%s: %s", b.labelTX, text))
|
||||
b.txLineActive = true
|
||||
} else {
|
||||
// Continue current line (no prefix)
|
||||
scopedLogger.Info().Msg("Emitting TX data to sink without prefix")
|
||||
scopedLogger.Trace().Msg("Emitting TX data to sink without prefix")
|
||||
b.emitToTerminal(text)
|
||||
}
|
||||
|
||||
|
|
@ -455,14 +455,14 @@ func (b *ConsoleBroker) flushPendingTX() {
|
|||
}
|
||||
|
||||
func (b *ConsoleBroker) lineSep() string {
|
||||
switch b.norm.CRLF {
|
||||
case CRLF_CRLF:
|
||||
switch b.norm.LineEnding {
|
||||
case LineEnding_CRLF:
|
||||
return "\r\n"
|
||||
case CRLF_LFCR:
|
||||
case LineEnding_LFCR:
|
||||
return "\n\r"
|
||||
case CRLF_CR:
|
||||
case LineEnding_CR:
|
||||
return "\r"
|
||||
case CRLF_LF:
|
||||
case LineEnding_LF:
|
||||
return "\n"
|
||||
default:
|
||||
return "\n"
|
||||
|
|
@ -470,26 +470,26 @@ func (b *ConsoleBroker) lineSep() string {
|
|||
}
|
||||
|
||||
// splitAfterAnyEOL splits text into lines keeping the EOL with each piece.
|
||||
// For CRLFAsIs it treats \r, \n, \r\n, and \n\r as EOLs.
|
||||
// For LineEnding_AsIs it treats \r, \n, \r\n, and \n\r as EOLs.
|
||||
// For other modes it uses the normalized separator.
|
||||
func splitAfterAnyEOL(text string, mode CRLFMode) []string {
|
||||
func splitAfterAnyEOL(text string, mode LineEndingMode) []string {
|
||||
if text == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fast path for normalized modes
|
||||
switch mode {
|
||||
case CRLF_LF:
|
||||
case LineEnding_LF:
|
||||
return strings.SplitAfter(text, "\n")
|
||||
case CRLF_CR:
|
||||
case LineEnding_CR:
|
||||
return strings.SplitAfter(text, "\r")
|
||||
case CRLF_CRLF:
|
||||
case LineEnding_CRLF:
|
||||
return strings.SplitAfter(text, "\r\n")
|
||||
case CRLF_LFCR:
|
||||
case LineEnding_LFCR:
|
||||
return strings.SplitAfter(text, "\n\r")
|
||||
}
|
||||
|
||||
// CRLFAsIs: scan bytes and treat \r, \n, \r\n, \n\r as one boundary
|
||||
// LineEnding_AsIs: scan bytes and treat \r, \n, \r\n, \n\r as one boundary
|
||||
b := []byte(text)
|
||||
var parts []string
|
||||
start := 0
|
||||
|
|
@ -511,18 +511,18 @@ func splitAfterAnyEOL(text string, mode CRLFMode) []string {
|
|||
return parts
|
||||
}
|
||||
|
||||
func endsWithEOL(s string, mode CRLFMode) bool {
|
||||
func endsWithEOL(s string, mode LineEndingMode) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
switch mode {
|
||||
case CRLF_CRLF:
|
||||
case LineEnding_CRLF:
|
||||
return strings.HasSuffix(s, "\r\n")
|
||||
case CRLF_LFCR:
|
||||
case LineEnding_LFCR:
|
||||
return strings.HasSuffix(s, "\n\r")
|
||||
case CRLF_LF:
|
||||
case LineEnding_LF:
|
||||
return strings.HasSuffix(s, "\n")
|
||||
case CRLF_CR:
|
||||
case LineEnding_CR:
|
||||
return strings.HasSuffix(s, "\r")
|
||||
default: // AsIs: any of \r, \n, \r\n, \n\r
|
||||
return strings.HasSuffix(s, "\r\n") ||
|
||||
|
|
@ -606,7 +606,7 @@ func (m *SerialMux) Close() { close(m.done) }
|
|||
func (m *SerialMux) SetEchoEnabled(v bool) { m.echoEnabled.Store(v) }
|
||||
|
||||
func (m *SerialMux) Enqueue(payload []byte, source string, requestEcho bool) {
|
||||
serialLogger.Info().Str("src", source).Bool("echo", requestEcho).Msg("Enqueuing TX data to serial port")
|
||||
serialLogger.Trace().Str("src", source).Bool("echo", requestEcho).Msg("Enqueuing TX data to serial port")
|
||||
m.txQ <- txFrame{payload: append([]byte(nil), payload...), source: source, echo: requestEcho}
|
||||
}
|
||||
|
||||
|
|
@ -627,7 +627,7 @@ func (m *SerialMux) reader() {
|
|||
continue
|
||||
}
|
||||
if n > 0 && m.broker != nil {
|
||||
scopedLogger.Info().Msg("Sending RX data to console broker")
|
||||
scopedLogger.Trace().Msg("Sending RX data to console broker")
|
||||
m.broker.Enqueue(consoleEvent{kind: evRX, data: append([]byte(nil), buf[:n]...)})
|
||||
}
|
||||
}
|
||||
|
|
@ -641,14 +641,14 @@ func (m *SerialMux) writer() {
|
|||
case <-m.done:
|
||||
return
|
||||
case f := <-m.txQ:
|
||||
scopedLogger.Info().Msg("Writing TX data to serial port")
|
||||
scopedLogger.Trace().Msg("Writing TX data to serial port")
|
||||
if _, err := m.port.Write(f.payload); err != nil {
|
||||
scopedLogger.Warn().Err(err).Str("src", f.source).Msg("serial write failed")
|
||||
continue
|
||||
}
|
||||
// echo (if requested AND globally enabled)
|
||||
if f.echo && m.echoEnabled.Load() && m.broker != nil {
|
||||
scopedLogger.Info().Msg("Sending TX echo to console broker")
|
||||
scopedLogger.Trace().Msg("Sending TX echo to console broker")
|
||||
m.broker.Enqueue(consoleEvent{kind: evTX, data: append([]byte(nil), f.payload...)})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue