feat: complete session preferences and PAM fixture gates

This commit is contained in:
2026-09-26 02:44:29 +05:30
parent 83345a1842
commit ee6186ed0b
34 changed files with 801 additions and 327 deletions

View File

@@ -18,8 +18,6 @@ import (
"latchd/tui/bg"
)
// ── pixelated user avatar ───────────────────────────────────────────────
var avatarArt = []string{
` ▄▄▄▄▄ `,
` ▄███████▄ `,
@@ -31,8 +29,6 @@ var avatarArt = []string{
` ██ ██ `,
}
// ── messages ─────────────────────────────────────────────────────────────
type tickMsg time.Time
type authResult struct {
@@ -57,8 +53,6 @@ const (
modeAdminPass
)
// ── result (sent back to main on successful login) ──────────────────────
type Result struct {
Username string
Password string
@@ -67,8 +61,6 @@ type Result struct {
Preview bool
}
// ── model ───────────────────────────────────────────────────────────────
type Model struct {
cfg config.Config
preview bool
@@ -137,8 +129,6 @@ func (m *Model) Init() tea.Cmd {
return tea.Batch(tick(), tea.EnterAltScreen)
}
// ── update ──────────────────────────────────────────────────────────────
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
@@ -222,8 +212,6 @@ func (m *Model) handleBypass(msg adminBypassResult) (tea.Model, tea.Cmd) {
return m, nil
}
// ── keys ────────────────────────────────────────────────────────────────
func (m *Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
k := msg.String()
@@ -392,8 +380,6 @@ func (m *Model) selectedEnv() session.PostLoginEnv {
return m.envs[m.selIdx]
}
// ── commands ────────────────────────────────────────────────────────────
func (m *Model) loginCmd() tea.Cmd {
if m.preview {
return func() tea.Msg { time.Sleep(800 * time.Millisecond); return authResult{ok: true} }
@@ -422,14 +408,10 @@ func (m *Model) adminBypassCmd() tea.Cmd {
}
}
// ── queries ────────────────────────────────────────────────────────────
func (m *Model) Result() *Result { return m.result }
func (m *Model) Quitting() bool { return m.quitting }
func (m *Model) SetAutomata(a *bg.Automata) { m.automata = a }
// ── view ───────────────────────────────────────────────────────────────
var (
accent = lipgloss.Color("208") // orange
subtle = lipgloss.Color("8") // grey
@@ -527,7 +509,6 @@ func overlayCenter(w, h int, content string) string {
func (m *Model) renderLayout() string {
clock := m.renderClock()
// top bar: power hints on left, clock on right
var topParts []string
topParts = append(topParts, m.renderPowerHints())
if clock != "" {
@@ -535,24 +516,19 @@ func (m *Model) renderLayout() string {
}
top := strings.Join(topParts, " ")
// avatar + form side by side
avatar := m.renderAvatar()
form := m.renderForm()
mid := lipgloss.JoinHorizontal(lipgloss.Top, avatar, " ", form)
// DE switcher with specifier label
sw := m.renderSwitcher()
// caps lock
caps := m.renderCapsLock()
// admin unlock
var admin string
if m.mode == modeAdminUser || m.mode == modeAdminPass {
admin = m.renderAdminForm()
}
// status
st := m.renderStatus()
var parts []string
@@ -655,7 +631,6 @@ func (m *Model) renderSwitcher() string {
var parts []string
// session specifier (like "x11", "wayland")
spec := swDot.Render(m.envs[m.selIdx].Kind + " ")
if cfg.ShowMovers && m.selIdx > 0 {
@@ -679,7 +654,6 @@ func (m *Model) renderSwitcher() string {
swLine := lipgloss.JoinHorizontal(lipgloss.Center, parts...)
// Dot indicators
var dots []string
for i := 0; i < len(m.envs); i++ {
if i == m.selIdx {
@@ -747,8 +721,6 @@ func (m *Model) renderStatus() string {
return st.Align(lipgloss.Center).Width(70).Render(m.status)
}
// ── caps lock detection ────────────────────────────────────────────────
const kdgkbled = 0x4B64
func capsLockOn() bool {
@@ -762,8 +734,6 @@ func capsLockOn() bool {
return leds&0x04 != 0
}
// ── field editing ──────────────────────────────────────────────────────
func handleField(k string, buf *string, cur, scroll *int) {
switch {
case k == "backspace" || k == "ctrl+h":
@@ -846,8 +816,6 @@ func abs(n int) int {
return n
}
// ── tick ────────────────────────────────────────────────────────────────
func tick() tea.Cmd {
return tea.Tick(time.Second/30, func(t time.Time) tea.Msg {
return tickMsg(t)