This repository has been archived on 2024-03-03. You can view files and clone it, but cannot push or open issues or pull requests.
adlin/pkg/login-app/cmd/cinematic.go

259 lines
6.2 KiB
Go

package main
import (
"log"
"math/rand"
"os"
"os/signal"
"strings"
"time"
"github.com/gdamore/tcell/v2"
)
var screen tcell.Screen
// just basic alphanumeric characters
var characters = []rune{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
}
// streamDisplays by column number
var streamDisplaysByColumn = make(map[int]*StreamDisplay)
// current sizes
var curSizes sizes
// channel used to notify StreamDisplayManager
var sizesUpdateCh = make(chan sizes)
// struct sizes contains terminal sizes (in amount of characters)
type sizes struct {
width int
height int
curStreamsPerStreamDisplay int // current amount of streams per display allowed
}
// set the sizes and notify StreamDisplayManager
func (s *sizes) setSizes(width int, height int) {
s.width = width
s.height = height
s.curStreamsPerStreamDisplay = 1 + height/10
}
func gomatrix() {
screen.SetStyle(tcell.StyleDefault.
Background(tcell.ColorBlack).
Foreground(tcell.ColorBlack))
screen.Clear()
// StreamDisplay manager
go func() {
var lastWidth int
for newSizes := range sizesUpdateCh {
diffWidth := newSizes.width - lastWidth
if diffWidth == 0 {
// same column size, wait for new information
continue
}
if diffWidth > 0 {
for newColumn := lastWidth; newColumn < newSizes.width; newColumn++ {
// create stream display
sd := &StreamDisplay{
column: newColumn,
stopCh: make(chan bool, 1),
streams: make(map[*Stream]bool),
newStream: make(chan bool, 1), // will only be filled at start and when a spawning stream has it's tail released
}
streamDisplaysByColumn[newColumn] = sd
// start StreamDisplay in goroutine
go sd.run()
// create first new stream
sd.newStream <- true
}
lastWidth = newSizes.width
}
if diffWidth < 0 {
for closeColumn := lastWidth - 1; closeColumn > newSizes.width; closeColumn-- {
// get sd
sd := streamDisplaysByColumn[closeColumn]
// delete from map
delete(streamDisplaysByColumn, closeColumn)
// inform sd that it's being closed
sd.stopCh <- true
}
lastWidth = newSizes.width
}
}
}()
// set initial sizes
curSizes.setSizes(screen.Size())
sizesUpdateCh <- curSizes
// flusher flushes the termbox every x milliseconds
curFPS := 25
fpsSleepTime := time.Duration(1000000/curFPS) * time.Microsecond
go func() {
for {
time.Sleep(fpsSleepTime)
screen.Show()
}
}()
// make chan for tembox events and run poller to send events on chan
eventChan := make(chan tcell.Event)
go func() {
for {
event := screen.PollEvent()
eventChan <- event
}
}()
// register signals to channel
sigChan := make(chan os.Signal)
signal.Notify(sigChan, os.Interrupt, os.Kill)
maxRun := time.After(8 * time.Second)
stopRun := time.After(12 * time.Second)
// handle tcell events and unix signals
EVENTS:
for {
// select for either event or signal
select {
case event := <-eventChan:
// switch on event type
switch ev := event.(type) {
case *tcell.EventKey:
switch ev.Key() {
case tcell.KeyCtrlZ:
break EVENTS
case tcell.KeyCtrlL:
screen.Sync()
case tcell.KeyRune:
switch ev.Rune() {
case 'q':
break EVENTS
case 'c':
screen.Clear()
}
}
case *tcell.EventError: // quit
log.Fatalf("Quitting because of tcell error: %v", ev.Error())
}
case <-maxRun:
for _, sd := range streamDisplaysByColumn {
sd.stopCh <- true
}
case <-stopRun:
break EVENTS
}
}
}
func displayLine(line string, textStyle tcell.Style, x, y, speed int) int {
for _, r := range line {
screen.SetCell(x, y, textStyle, r)
x += 1
screen.Show()
if r == ' ' {
time.Sleep(time.Duration(speed/2+rand.Intn(speed/2)) * time.Millisecond)
} else if r == '.' || r == ',' {
time.Sleep(time.Duration(speed*5+rand.Intn(speed*25/10)) * time.Millisecond)
} else {
time.Sleep(time.Duration(speed+rand.Intn(speed*2)) * time.Millisecond)
}
}
return x
}
func blinkingCursor(textStyle tcell.Style, x, y, nb int) {
for i := 0; i < nb; i += 1 {
if i%2 == 0 {
screen.SetCell(x, y, textStyle, '_')
} else {
screen.SetCell(x, y, textStyle, ' ')
}
screen.Show()
time.Sleep(750 * time.Millisecond)
}
}
func scenario(login string) {
blackStyle := tcell.StyleDefault.
Foreground(tcell.ColorBlack).
Background(tcell.ColorBlack)
textStyle := blackStyle.Foreground(tcell.ColorGreen)
displayLine("Wake up, "+login+"...", textStyle, 1, 2, 100)
time.Sleep(1000 * time.Millisecond)
screen.Clear()
displayLine("The Matrix has you...", textStyle, 1, 2, 100)
time.Sleep(1500 * time.Millisecond)
screen.Clear()
blinkingCursor(textStyle, 1, 2, 2)
displayLine("Pour t'empêcher de sortir de la Matrice, des agents", textStyle, 1, 2, 25)
displayLine("ont piégé ton poste de travail et le réseau environnant.", textStyle, 1, 3, 25)
blinkingCursor(textStyle, 1, 5, 3)
displayLine("J'ai peur que tu ne doives te débrouiller tout.e seul.e", textStyle, 1, 5, 25)
displayLine("pour retrouver la route vers Internet, d'où l'on pourra", textStyle, 1, 6, 25)
pos := displayLine("t'extraire sans risque.", textStyle, 1, 7, 25)
blinkingCursor(textStyle, pos, 7, 6)
displayLine("Ils te tiennent !", textStyle, 1, 10, 15)
displayLine("Je redémarre ta machine pour effacer notre échange.", textStyle, 1, 11, 25)
pos = displayLine("Bonne chance.", textStyle, 1, 13, 50)
blinkingCursor(textStyle, pos, 13, 6)
displayLine("Au fait, le pass root est: hax&i6aes2so5niec8XeeLei_", textStyle, 1, 15, 2)
time.Sleep(400 * time.Millisecond)
}
func runCinematic(login string) {
var err error
// initialize tcell
if screen, err = tcell.NewScreen(); err != nil {
os.Exit(1)
}
err = screen.Init()
if err != nil {
os.Exit(1)
}
gomatrix()
screen.Clear()
// Keep only the first part of the login (firstname)
login = strings.SplitN(login, ".", 2)[0]
scenario(login)
// close down
screen.Fini()
}