login-app: use tcell/tview to make the form and add cinematic
This commit is contained in:
parent
1d8146d8ad
commit
4ce6f09a8d
8 changed files with 520 additions and 332 deletions
258
pkg/login-app/cmd/cinematic.go
Normal file
258
pkg/login-app/cmd/cinematic.go
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
Reference in a new issue