Files

79 lines
3.1 KiB
Go

// Fenrir console screen reader.
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
//go:build linux && cgo
package console
import (
"encoding/binary"
"fmt"
"unicode/utf8"
"git.stormux.org/storm/fenrir/internal/screen"
)
// decode_snapshot uses the VCSA header and native-endian UTF-32 VCSU cells. Glyph
// numbers from VCSA are deliberately not treated as Unicode characters.
func decode_snapshot(tty int, header, text []byte) (screen.Snapshot, error) {
if len(header) < 4 {
return screen.Snapshot{}, fmt.Errorf("short VCSA header")
}
snapshot := screen.Snapshot{Terminal: tty, Active: true, Rows: int(header[0]), Cols: int(header[1]), X: int(header[2]), Y: int(header[3])}
if snapshot.Rows == 0 || snapshot.Cols == 0 || snapshot.X >= snapshot.Cols || snapshot.Y >= snapshot.Rows {
return screen.Snapshot{}, fmt.Errorf("invalid console geometry or cursor")
}
count := snapshot.Rows * snapshot.Cols
if len(text) != count*4 {
return screen.Snapshot{}, fmt.Errorf("VCSU size %d does not match %dx%d console", len(text), snapshot.Cols, snapshot.Rows)
}
snapshot.Cells = make([]rune, count)
for index := range snapshot.Cells {
value := binary.NativeEndian.Uint32(text[index*4:])
switch {
case value == 0:
snapshot.Cells[index] = ' '
case value > utf8.MaxRune || value >= 0xd800 && value <= 0xdfff:
snapshot.Cells[index] = utf8.RuneError
default:
snapshot.Cells[index] = rune(value)
}
}
return snapshot, nil
}
func decode_attributes(snapshot *screen.Snapshot, data []byte) error {
if len(data) != 4+len(snapshot.Cells)*2 {
return fmt.Errorf("VCSA size %d does not match %dx%d console", len(data), snapshot.Cols, snapshot.Rows)
}
snapshot.Attributes = make([]screen.Attribute, len(snapshot.Cells))
for index := range snapshot.Attributes {
// VCSA stores native-endian glyph/attribute words. Glyph bytes remain
// unused: Unicode always comes from VCSU, including non-ASCII fonts.
attribute := binary.NativeEndian.Uint16(data[4+index*2:]) >> 8
// VGA palette order swaps ANSI red/blue. Intensity is a color, not
// proof of bold; VCSA cannot recover the original SGR font styles.
ansi := func(value uint16) uint32 { return uint32(value&10 | (value&1)<<2 | (value&4)>>2) }
style := uint32(0)
if attribute&128 != 0 {
style = screen.ConsoleHigh
}
snapshot.Attributes[index] = screen.Attribute{Foreground: screen.ColorIndexed | ansi(attribute&15), Background: screen.ColorIndexed | ansi((attribute>>4)&7), Style: style}
}
return nil
}