login-app: use tcell/tview to make the form and add cinematic
This commit is contained in:
parent
1d8146d8ad
commit
4ce6f09a8d
2
Makefile
2
Makefile
|
@ -1,6 +1,6 @@
|
|||
tuto1: token-validator/token-validator server.iso
|
||||
|
||||
pkg/login-app: pkg/login-app/cmd/login.go pkg/login-app/cmd/dialog-checklogin.go pkg/login-app/cmd/cmd pkg/login-app/cmd/dialog-login.go pkg/login-app/cmd/login-app pkg/login-app/cmd/dialog-errmsg.go pkg/login-app/cmd/main.go pkg/login-app/cmd/dialog-reboot.go pkg/login-app/cmd/debug.log pkg/login-app/build.yml pkg/login-app/Dockerfile
|
||||
pkg/login-app: pkg/login-app/cmd/login.go pkg/login-app/cmd/dialog-checklogin.go pkg/login-app/cmd/cmd pkg/login-app/cmd/dialog-login.go pkg/login-app/cmd/login-app pkg/login-app/cmd/dialog-errmsg.go pkg/login-app/cmd/main.go pkg/login-app/cmd/stream.go pkg/login-app/cmd/cinematic.go pkg/login-app/build.yml pkg/login-app/Dockerfile
|
||||
linuxkit pkg build -org nemunaire pkg/login-app/
|
||||
#linuxkit pkg push -org nemunaire --sign=false pkg/login-app/
|
||||
touch pkg/login-app
|
||||
|
|
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()
|
||||
}
|
|
@ -4,76 +4,41 @@ import (
|
|||
"math"
|
||||
"time"
|
||||
|
||||
ui "github.com/VladimirMarkelov/clui"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type CheckDialog struct {
|
||||
View *ui.Window
|
||||
func CreateCheckDialog(app *tview.Application) *tview.Box {
|
||||
progress := 0
|
||||
box := tview.NewBox().
|
||||
SetBorder(true).
|
||||
SetTitle(" SRS Adlin - Login ").
|
||||
SetDrawFunc(func(screen tcell.Screen, x int, y int, width int, height int) (int, int, int, int) {
|
||||
tview.Print(screen, "Please wait", x, y+2, width-2, tview.AlignCenter, tcell.ColorYellow)
|
||||
tview.Print(screen, "Connecting to login server...", x, y+3, width-2, tview.AlignCenter, tcell.ColorYellow)
|
||||
|
||||
result int
|
||||
beforeClose func(ui.Event)
|
||||
onClose func()
|
||||
}
|
||||
|
||||
func CreateCheckDialog(title, username, password string) *CheckDialog {
|
||||
dlg := new(CheckDialog)
|
||||
|
||||
sWidth, sHeight := ui.ScreenSize()
|
||||
wWidth, wHeight := 40, 5
|
||||
dlg.View = ui.AddWindow(sWidth/2-wWidth/2, sHeight/2-wHeight/2, wWidth, wHeight, title)
|
||||
ui.WindowManager().BeginUpdate()
|
||||
defer ui.WindowManager().EndUpdate()
|
||||
|
||||
dlg.View.SetModal(true)
|
||||
dlg.View.SetSizable(false)
|
||||
dlg.View.SetTitleButtons(ui.ButtonDefault)
|
||||
dlg.View.SetPack(ui.Vertical)
|
||||
|
||||
textfrm := ui.CreateFrame(dlg.View, 1, 1, ui.BorderNone, ui.Fixed)
|
||||
textfrm.SetPaddings(1, 1)
|
||||
textfrm.SetPack(ui.Vertical)
|
||||
textfrm.SetGaps(2, 1)
|
||||
ui.CreateLabel(textfrm, ui.AutoSize, ui.AutoSize, " Please wait", ui.Fixed)
|
||||
|
||||
progress := ui.CreateProgressBar(textfrm, ui.AutoSize, ui.AutoSize, 1)
|
||||
progress.SetLimits(0, 100)
|
||||
|
||||
ui.CreateLabel(textfrm, ui.AutoSize, ui.AutoSize, "Connecting to login server...", ui.Fixed)
|
||||
|
||||
dlg.View.OnKeyDown(func(ev ui.Event, data interface{}) bool {
|
||||
if ev.Key == 0 && ev.Msg == "login-complete" {
|
||||
if dlg.beforeClose != nil {
|
||||
dlg.beforeClose(ev)
|
||||
// Draw the progress bar
|
||||
for cx := x + 2; cx < x+width-2; cx++ {
|
||||
if (cx-x)*100/(width-2) > progress {
|
||||
screen.SetContent(cx, y+5, tview.BoxDrawingsLightHorizontal, nil, tcell.StyleDefault.Background(tcell.ColorBlack))
|
||||
} else {
|
||||
screen.SetContent(cx, y+5, ' ', nil, tcell.StyleDefault.Background(tcell.ColorBlue))
|
||||
}
|
||||
}
|
||||
|
||||
ui.WindowManager().DestroyWindow(dlg.View)
|
||||
ui.WindowManager().BeginUpdate()
|
||||
return x, y, width, height
|
||||
})
|
||||
|
||||
closeFunc := dlg.onClose
|
||||
ui.WindowManager().EndUpdate()
|
||||
if closeFunc != nil {
|
||||
closeFunc()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, nil)
|
||||
|
||||
go func() {
|
||||
if ok, err := checkLogin(username, password); ok {
|
||||
ui.PutEvent(ui.Event{Type: ui.EventKey, Msg: "login-complete"})
|
||||
} else {
|
||||
ui.PutEvent(ui.Event{Type: ui.EventKey, Msg: "login-complete", Err: err})
|
||||
}
|
||||
}()
|
||||
app.SetRoot(modal(box, 40, 8), true)
|
||||
app.SetFocus(box)
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 422; i += 1 {
|
||||
progress.SetValue(int(math.Floor(math.Log(float64(i)*8)*16 - 30)))
|
||||
progress = int(math.Floor(math.Log(float64(i)*8)*16 - 30))
|
||||
time.Sleep(64 * time.Millisecond)
|
||||
ui.PutEvent(ui.Event{Type: ui.EventRedraw})
|
||||
app.Draw()
|
||||
}
|
||||
}()
|
||||
|
||||
return dlg
|
||||
return box
|
||||
}
|
||||
|
|
|
@ -1,85 +1,36 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
ui "github.com/VladimirMarkelov/clui"
|
||||
term "github.com/nsf/termbox-go"
|
||||
"fmt"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type ErrMsgDialog struct {
|
||||
View *ui.Window
|
||||
func CreateErrMsgDialog(app *tview.Application, err error) {
|
||||
textView := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetRegions(true).
|
||||
SetChangedFunc(func() {
|
||||
app.Draw()
|
||||
})
|
||||
|
||||
result int
|
||||
beforeClose func()
|
||||
onClose func()
|
||||
}
|
||||
|
||||
func CreateErrMsgDialog(title string, err error) *ErrMsgDialog {
|
||||
dlg := new(ErrMsgDialog)
|
||||
|
||||
sWidth, sHeight := ui.ScreenSize()
|
||||
wWidth, wHeight := 60, 10
|
||||
dlg.View = ui.AddWindow(sWidth/2-wWidth/2, sHeight/2-wHeight/2, wWidth, wHeight, title)
|
||||
ui.WindowManager().BeginUpdate()
|
||||
defer ui.WindowManager().EndUpdate()
|
||||
|
||||
dlg.View.SetModal(true)
|
||||
dlg.View.SetSizable(false)
|
||||
dlg.View.SetTitleButtons(ui.ButtonDefault)
|
||||
dlg.View.SetPack(ui.Vertical)
|
||||
|
||||
textfrm := ui.CreateFrame(dlg.View, 1, 1, ui.BorderNone, ui.Fixed)
|
||||
textfrm.SetPaddings(1, 1)
|
||||
textfrm.SetPack(ui.Vertical)
|
||||
textfrm.SetGaps(2, 1)
|
||||
|
||||
lbl := ui.CreateLabel(textfrm, ui.AutoSize, ui.AutoSize, "An error occurs:", ui.Fixed)
|
||||
lbl.SetTextColor(ui.ColorRedBold)
|
||||
|
||||
tv := ui.CreateTextView(textfrm, ui.AutoSize, 4, ui.Fixed)
|
||||
tv.SetWordWrap(true)
|
||||
tv.SetText([]string{err.Error()})
|
||||
tv.SetTextColor(ui.ColorWhite)
|
||||
tv.SetBackColor(ui.ColorBlack)
|
||||
|
||||
ui.CreateLabel(textfrm, ui.AutoSize, ui.AutoSize, "Please try again.", ui.Fixed)
|
||||
|
||||
btnOk := ui.CreateButton(textfrm, 20, 4, "Ok", 1)
|
||||
|
||||
btnOk.OnClick(func(ev ui.Event) {
|
||||
if dlg.beforeClose != nil {
|
||||
dlg.beforeClose()
|
||||
}
|
||||
|
||||
ui.WindowManager().DestroyWindow(dlg.View)
|
||||
ui.WindowManager().BeginUpdate()
|
||||
|
||||
closeFunc := dlg.onClose
|
||||
ui.WindowManager().EndUpdate()
|
||||
if closeFunc != nil {
|
||||
closeFunc()
|
||||
}
|
||||
})
|
||||
|
||||
dlg.View.OnKeyDown(func(ev ui.Event, data interface{}) bool {
|
||||
if ev.Key == term.KeyEnter {
|
||||
if dlg.beforeClose != nil {
|
||||
dlg.beforeClose()
|
||||
}
|
||||
|
||||
ui.WindowManager().DestroyWindow(dlg.View)
|
||||
ui.WindowManager().BeginUpdate()
|
||||
|
||||
closeFunc := dlg.onClose
|
||||
ui.WindowManager().EndUpdate()
|
||||
if closeFunc != nil {
|
||||
closeFunc()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, nil)
|
||||
|
||||
ui.ActivateControl(dlg.View, btnOk)
|
||||
|
||||
return dlg
|
||||
form := tview.NewForm().
|
||||
AddButton("Authenticate me", func() {
|
||||
askLogin(app)
|
||||
})
|
||||
|
||||
flex := tview.NewFlex().
|
||||
AddItem(nil, 0, 1, false).
|
||||
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
|
||||
AddItem(textView, 12, 1, false).
|
||||
AddItem(form, 1, 1, false), 37, 1, false).
|
||||
AddItem(nil, 0, 1, false)
|
||||
|
||||
flex.SetBorder(true).
|
||||
SetTitle(" SRS Adlin - Login ")
|
||||
|
||||
fmt.Fprintf(textView, "\nAn error occurs:\n\n[red]%s\n\n[yellow]Press Enter to retry", err.Error())
|
||||
|
||||
app.SetRoot(modal(flex, 42, 15), true)
|
||||
app.SetFocus(form)
|
||||
}
|
||||
|
|
|
@ -1,124 +1,24 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
ui "github.com/VladimirMarkelov/clui"
|
||||
term "github.com/nsf/termbox-go"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const (
|
||||
LoginOk = iota
|
||||
LoginCanceled
|
||||
LoginInvalid
|
||||
)
|
||||
func CreateLoginDialog(app *tview.Application, next func(username, password string)) {
|
||||
var form *tview.Form
|
||||
form = tview.NewForm().
|
||||
AddInputField("Login", "", 27, nil, nil).
|
||||
AddPasswordField("Password", "", 27, '*', nil).
|
||||
AddButton("Authenticate me", func() {
|
||||
next(
|
||||
form.GetFormItemByLabel("Login").(*tview.InputField).GetText(),
|
||||
form.GetFormItemByLabel("Password").(*tview.InputField).GetText(),
|
||||
)
|
||||
})
|
||||
form.SetBorder(true).SetTitle(" SRS Adlin - Login ")
|
||||
|
||||
type LoginDialog struct {
|
||||
View *ui.Window
|
||||
Username string
|
||||
Password string
|
||||
Action int
|
||||
app.SetRoot(modal(form, 40, 9), true)
|
||||
app.SetFocus(form)
|
||||
|
||||
result int
|
||||
beforeClose func()
|
||||
onClose func()
|
||||
onCheck func(string, string) bool
|
||||
}
|
||||
|
||||
func CreateLoginDialog(title string) *LoginDialog {
|
||||
dlg := new(LoginDialog)
|
||||
|
||||
sWidth, sHeight := ui.ScreenSize()
|
||||
wWidth, wHeight := 40, 15
|
||||
dlg.View = ui.AddWindow(sWidth/2-wWidth/2, sHeight/2-wHeight/2, wWidth, wHeight, title)
|
||||
ui.WindowManager().BeginUpdate()
|
||||
defer ui.WindowManager().EndUpdate()
|
||||
|
||||
dlg.View.SetModal(true)
|
||||
dlg.View.SetSizable(false)
|
||||
dlg.View.SetTitleButtons(ui.ButtonDefault)
|
||||
dlg.View.SetPack(ui.Vertical)
|
||||
|
||||
userfrm := ui.CreateFrame(dlg.View, 1, 1, ui.BorderNone, ui.Fixed)
|
||||
userfrm.SetPaddings(1, 1)
|
||||
userfrm.SetPack(ui.Horizontal)
|
||||
userfrm.SetGaps(1, 0)
|
||||
ui.CreateLabel(userfrm, ui.AutoSize, ui.AutoSize, " Login", ui.Fixed)
|
||||
edUser := ui.CreateEditField(userfrm, 20, "", 1)
|
||||
|
||||
passfrm := ui.CreateFrame(dlg.View, 1, 1, ui.BorderNone, 1)
|
||||
passfrm.SetPaddings(1, 1)
|
||||
passfrm.SetPack(ui.Horizontal)
|
||||
passfrm.SetGaps(1, 0)
|
||||
ui.CreateLabel(passfrm, ui.AutoSize, ui.AutoSize, "Password", ui.Fixed)
|
||||
edPass := ui.CreateEditField(passfrm, 20, "", 1)
|
||||
edPass.SetPasswordMode(true)
|
||||
|
||||
filler := ui.CreateFrame(dlg.View, 1, 1, ui.BorderNone, 1)
|
||||
filler.SetPack(ui.Horizontal)
|
||||
lbRes := ui.CreateLabel(filler, ui.AutoSize, ui.AutoSize, "", 1)
|
||||
|
||||
blist := ui.CreateFrame(dlg.View, 1, 1, ui.BorderNone, ui.Fixed)
|
||||
blist.SetPack(ui.Horizontal)
|
||||
blist.SetPaddings(1, 1)
|
||||
btnOk := ui.CreateButton(blist, 20, 4, "Authenticate me", 1)
|
||||
|
||||
btnOk.OnClick(func(ev ui.Event) {
|
||||
if dlg.onCheck != nil && !dlg.onCheck(edUser.Title(), edPass.Title()) {
|
||||
lbRes.SetTitle("Invalid username or password")
|
||||
dlg.Action = LoginInvalid
|
||||
return
|
||||
}
|
||||
|
||||
dlg.Action = LoginOk
|
||||
if dlg.onCheck == nil {
|
||||
dlg.Username = edUser.Title()
|
||||
dlg.Password = edPass.Title()
|
||||
}
|
||||
|
||||
if dlg.beforeClose != nil {
|
||||
dlg.beforeClose()
|
||||
}
|
||||
ui.WindowManager().DestroyWindow(dlg.View)
|
||||
ui.WindowManager().BeginUpdate()
|
||||
|
||||
closeFunc := dlg.onClose
|
||||
ui.WindowManager().EndUpdate()
|
||||
if closeFunc != nil {
|
||||
closeFunc()
|
||||
}
|
||||
})
|
||||
|
||||
dlg.View.OnKeyDown(func(ev ui.Event, data interface{}) bool {
|
||||
if ev.Key == term.KeyEnter {
|
||||
if edUser.Title() == "" {
|
||||
ui.ActivateControl(dlg.View, edUser)
|
||||
} else if edPass.Title() == "" {
|
||||
ui.ActivateControl(dlg.View, edPass)
|
||||
} else {
|
||||
if dlg.beforeClose != nil {
|
||||
dlg.beforeClose()
|
||||
}
|
||||
ui.WindowManager().DestroyWindow(dlg.View)
|
||||
ui.WindowManager().BeginUpdate()
|
||||
|
||||
closeFunc := dlg.onClose
|
||||
ui.WindowManager().EndUpdate()
|
||||
if closeFunc != nil {
|
||||
closeFunc()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, nil)
|
||||
|
||||
edUser.OnChange(func(ev ui.Event) {
|
||||
lbRes.SetTitle("")
|
||||
})
|
||||
edPass.OnChange(func(ev ui.Event) {
|
||||
lbRes.SetTitle("")
|
||||
})
|
||||
|
||||
ui.ActivateControl(dlg.View, edUser)
|
||||
|
||||
return dlg
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
ui "github.com/VladimirMarkelov/clui"
|
||||
)
|
||||
|
||||
type RebootDialog struct {
|
||||
View *ui.Window
|
||||
}
|
||||
|
||||
func CreateRebootDialog(title, login string) *RebootDialog {
|
||||
dlg := new(RebootDialog)
|
||||
|
||||
sWidth, sHeight := ui.ScreenSize()
|
||||
wWidth, wHeight := 48, 10
|
||||
dlg.View = ui.AddWindow(sWidth/2-wWidth/2, sHeight/2-wHeight/2, wWidth, wHeight, title)
|
||||
ui.WindowManager().BeginUpdate()
|
||||
defer ui.WindowManager().EndUpdate()
|
||||
|
||||
dlg.View.SetModal(true)
|
||||
dlg.View.SetSizable(false)
|
||||
dlg.View.SetTitleButtons(ui.ButtonDefault)
|
||||
dlg.View.SetPack(ui.Vertical)
|
||||
|
||||
textfrm := ui.CreateFrame(dlg.View, 1, 1, ui.BorderNone, ui.Fixed)
|
||||
textfrm.SetPaddings(1, 1)
|
||||
textfrm.SetPack(ui.Vertical)
|
||||
textfrm.SetGaps(2, 1)
|
||||
|
||||
lbl1 := ui.CreateLabel(textfrm, ui.AutoSize, ui.AutoSize, " You are now successfully logged in as:", ui.Fixed)
|
||||
lbl1.SetTextColor(ui.ColorWhiteBold)
|
||||
|
||||
for i := 0; i <= wWidth/2-len(login)/2; i += 1 {
|
||||
login = " " + login
|
||||
}
|
||||
lbl2 := ui.CreateLabel(textfrm, ui.AutoSize, ui.AutoSize, login, 1)
|
||||
lbl2.SetTextColor(ui.ColorGreenBold)
|
||||
|
||||
lbl3 := ui.CreateLabel(textfrm, 40, 2, " Your computer will automatically\n reboot in a few seconds...", ui.Fixed)
|
||||
lbl3.SetMultiline(true)
|
||||
|
||||
progress := ui.CreateProgressBar(textfrm, ui.AutoSize, ui.AutoSize, ui.Fixed)
|
||||
progress.SetLimits(0, 100)
|
||||
|
||||
lbl4 := ui.CreateLabel(textfrm, 40, 4, "\nThe challenge begins right after the reboot. Good luck!", ui.Fixed)
|
||||
lbl4.SetTextColor(ui.ColorWhiteBold)
|
||||
lbl4.SetMultiline(true)
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 100; i += 1 {
|
||||
progress.SetValue(i)
|
||||
ui.PutEvent(ui.Event{Type: ui.EventRedraw})
|
||||
time.Sleep(64 * time.Millisecond)
|
||||
}
|
||||
ui.WindowManager().DestroyWindow(dlg.View)
|
||||
}()
|
||||
|
||||
return dlg
|
||||
}
|
|
@ -1,19 +1,45 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
ui "github.com/VladimirMarkelov/clui"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const URLLogin = "https://auth.adlin.nemunai.re/login"
|
||||
|
||||
var logged = false
|
||||
var (
|
||||
loggedAs = ""
|
||||
)
|
||||
|
||||
func askLogin() (lgd *LoginDialog) {
|
||||
lgd = CreateLoginDialog(" SRS AdLin - Login ")
|
||||
func modal(p tview.Primitive, width, height int) tview.Primitive {
|
||||
return tview.NewFlex().
|
||||
AddItem(nil, 0, 1, false).
|
||||
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
|
||||
AddItem(nil, 0, 1, false).
|
||||
AddItem(p, height, 1, false).
|
||||
AddItem(nil, 0, 1, false), width, 1, false).
|
||||
AddItem(nil, 0, 1, false)
|
||||
}
|
||||
|
||||
lgd.beforeClose = func() {
|
||||
func askLogin(app *tview.Application) {
|
||||
CreateLoginDialog(app, func(username, password string) {
|
||||
// Display check dialog
|
||||
CreateCheckDialog(app)
|
||||
|
||||
go func() {
|
||||
if ok, err := checkLogin(username, password); ok {
|
||||
loggedAs = username
|
||||
app.Stop()
|
||||
} else {
|
||||
CreateErrMsgDialog(app, err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
/*lgd.beforeClose = func() {
|
||||
// Display next dialoag
|
||||
ckd := CreateCheckDialog(" SRS AdLin - Login ", lgd.Username, lgd.Password)
|
||||
|
||||
|
@ -28,20 +54,31 @@ func askLogin() (lgd *LoginDialog) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}*/
|
||||
}
|
||||
|
||||
func main() {
|
||||
ui.InitLibrary()
|
||||
defer ui.DeinitLibrary()
|
||||
// seed the rand package with time
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
askLogin()
|
||||
app := tview.NewApplication()
|
||||
|
||||
ui.MainLoop()
|
||||
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyCtrlQ {
|
||||
app.Stop()
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
if !logged {
|
||||
askLogin(app)
|
||||
|
||||
if err := app.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if loggedAs == "" {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
runCinematic(loggedAs)
|
||||
}
|
||||
|
|
138
pkg/login-app/cmd/stream.go
Normal file
138
pkg/login-app/cmd/stream.go
Normal file
|
@ -0,0 +1,138 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
// Stream updates a StreamDisplay with new data updates
|
||||
type Stream struct {
|
||||
display *StreamDisplay
|
||||
speed int
|
||||
length int
|
||||
headPos int
|
||||
tailPos int
|
||||
stopCh chan bool
|
||||
killCh chan bool
|
||||
headDone bool
|
||||
fini bool
|
||||
}
|
||||
|
||||
func (s *Stream) run() {
|
||||
blackStyle := tcell.StyleDefault.
|
||||
Foreground(tcell.ColorBlack).
|
||||
Background(tcell.ColorBlack)
|
||||
|
||||
midStyleA := blackStyle.Foreground(tcell.ColorGreen)
|
||||
midStyleB := blackStyle.Foreground(tcell.ColorLime)
|
||||
headStyleA := blackStyle.Foreground(tcell.ColorSilver)
|
||||
headStyleB := blackStyle.Foreground(tcell.ColorWhite)
|
||||
|
||||
var lastRune rune
|
||||
STREAM:
|
||||
for {
|
||||
select {
|
||||
case <-s.stopCh:
|
||||
s.fini = true
|
||||
case <-time.After(time.Duration(s.speed) * time.Millisecond):
|
||||
// add a new rune if there is space in the stream
|
||||
if !s.fini && !s.headDone && s.headPos <= curSizes.height {
|
||||
newRune := characters[rand.Intn(len(characters))]
|
||||
|
||||
// Making most of the green characters bright/bold...
|
||||
if rand.Intn(100) < 66 {
|
||||
screen.SetCell(s.display.column, s.headPos-1, midStyleA, lastRune)
|
||||
} else {
|
||||
screen.SetCell(s.display.column, s.headPos-1, midStyleB, lastRune)
|
||||
}
|
||||
|
||||
// ...and turning about a third of the heads from gray to white
|
||||
if rand.Intn(100) < 33 {
|
||||
screen.SetCell(s.display.column, s.headPos, headStyleA, newRune)
|
||||
} else {
|
||||
screen.SetCell(s.display.column, s.headPos, headStyleB, newRune)
|
||||
}
|
||||
lastRune = newRune
|
||||
s.headPos++
|
||||
} else {
|
||||
if s.fini && (s.length <= 0 || s.headPos <= 0) {
|
||||
break STREAM
|
||||
} else if s.fini && s.headPos < s.length {
|
||||
s.length = s.headPos
|
||||
}
|
||||
s.headDone = true
|
||||
}
|
||||
|
||||
// clear rune at the tail of the stream
|
||||
if s.tailPos > 0 || s.headPos >= s.length {
|
||||
if s.tailPos == 0 && !s.fini {
|
||||
// tail is being incremented for the first time. there is space for a new stream
|
||||
s.display.newStream <- true
|
||||
}
|
||||
if s.tailPos < curSizes.height {
|
||||
screen.SetCell(s.display.column, s.tailPos, blackStyle, ' ') //'\uFF60'
|
||||
s.tailPos++
|
||||
} else {
|
||||
break STREAM
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(s.display.streams, s)
|
||||
}
|
||||
|
||||
// StreamDisplay represents a vertical line in the terminal on which `Stream`s are displayed.
|
||||
// StreamDisplay also creates the Streams themselves
|
||||
type StreamDisplay struct {
|
||||
column int
|
||||
stopCh chan bool
|
||||
streams map[*Stream]bool
|
||||
streamsLock sync.Mutex
|
||||
newStream chan bool
|
||||
}
|
||||
|
||||
func (sd *StreamDisplay) run() {
|
||||
for {
|
||||
select {
|
||||
case <-sd.stopCh:
|
||||
// lock this SD forever
|
||||
sd.streamsLock.Lock()
|
||||
|
||||
// stop streams for this SD
|
||||
for s := range sd.streams {
|
||||
s.stopCh <- true
|
||||
}
|
||||
|
||||
// close this goroutine
|
||||
return
|
||||
|
||||
case <-sd.newStream:
|
||||
// have some wait before the first stream starts..
|
||||
time.Sleep(time.Duration(rand.Intn(9000)) * time.Millisecond)
|
||||
|
||||
// lock map
|
||||
sd.streamsLock.Lock()
|
||||
|
||||
// create new stream instance
|
||||
s := &Stream{
|
||||
display: sd,
|
||||
stopCh: make(chan bool),
|
||||
speed: 30 + rand.Intn(110),
|
||||
length: 10 + rand.Intn(8), // length of a stream is between 10 and 18 runes
|
||||
}
|
||||
|
||||
// store in streams map
|
||||
sd.streams[s] = true
|
||||
|
||||
// run the stream in a goroutine
|
||||
go s.run()
|
||||
|
||||
// unlock map
|
||||
sd.streamsLock.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user