259 lines
6.2 KiB
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()
|
|
}
|