package page import ( "fmt" "os/exec" "strconv" "strings" "github.com/go-gl/gl/v3.3-core/gl" "github.com/veandco/go-sdl2/sdl" "rhemawavetech.com/rhemawave/internal/colors" "rhemawavetech.com/rhemawave/internal/db" "rhemawavetech.com/rhemawave/internal/fontManager" "rhemawavetech.com/rhemawave/internal/shaderManager" ) type updateCheckState int const ( updateCheckStateChecking updateCheckState = iota updateCheckStateDone ) type UpdateCheckPage struct { Base textShader *shaderManager.Shader state updateCheckState // "Checking for Updates." / ".." / "..." checkingTextures [3]StringItem // "No updates available" and "Press F3 to return" doneMessage StringItem doneHint StringItem // Error message if service failed to start errorMessage StringItem hasError bool pollTimer float32 dotIndex int dotTimer float32 } func (p *UpdateCheckPage) Init(app ApplicationInterface) error { if err := p.Base.Init(app); err != nil { return fmt.Errorf("failed base page initialization: %w", err) } fm := fontManager.Get() sm := shaderManager.Get() font, ok := fm.GetFont("Roboto-Regular") if !ok { return fmt.Errorf("failed to get font") } p.textShader, ok = sm.Get("text") if !ok { return fmt.Errorf("failed to get text shader") } // Get scale baseFontSizeStr, err := db.GetConfigValue("init_font_size") if err != nil { return fmt.Errorf("retrieving init_font_size from db: %w", err) } baseFontSize, err := strconv.Atoi(baseFontSizeStr) if err != nil { return fmt.Errorf("parsing init_font_size: %w", err) } scale := float32(48) / float32(baseFontSize) // Create checking textures with animated dots dots := []string{"Checking for Updates.", "Checking for Updates..", "Checking for Updates..."} for i, text := range dots { item, err := NewStringItem(text, font, colors.White) if err != nil { return fmt.Errorf("creating checking texture %d: %w", i, err) } item.Scale(scale) p.checkingTextures[i] = item } // Create done textures hintScale := float32(32) / float32(baseFontSize) p.doneMessage, err = NewStringItem("No updates available", font, colors.White) if err != nil { return fmt.Errorf("creating done message: %w", err) } p.doneMessage.Scale(scale) p.doneHint, err = NewStringItem("Press F3 to return", font, colors.White) if err != nil { return fmt.Errorf("creating done hint: %w", err) } p.doneHint.Scale(hintScale) // Start the update service p.state = updateCheckStateChecking if err := exec.Command("systemctl", "start", "rhemawave-update.service").Run(); err != nil { p.errorMessage, _ = NewStringItem("Update service unavailable", font, colors.White) p.errorMessage.Scale(scale) p.hasError = true p.state = updateCheckStateDone } return nil } func (p *UpdateCheckPage) Update(dt float32) error { if p.state != updateCheckStateChecking { return nil } // Animate dots p.dotTimer += dt if p.dotTimer >= 0.5 { p.dotTimer -= 0.5 p.dotIndex = (p.dotIndex + 1) % 3 } // Poll service status every 2 seconds p.pollTimer += dt if p.pollTimer >= 2.0 { p.pollTimer -= 2.0 output, _ := exec.Command("systemctl", "is-active", "rhemawave-update.service").Output() status := strings.TrimSpace(string(output)) if status != "active" && status != "activating" { p.state = updateCheckStateDone } } return nil } func (p *UpdateCheckPage) HandleEvent(event *sdl.Event) error { if p.state != updateCheckStateDone { return nil } switch e := (*event).(type) { case *sdl.KeyboardEvent: if e.Type != sdl.KEYDOWN { return nil } switch e.Keysym.Sym { case sdl.K_F3, sdl.K_BACKSPACE: p.App.UnwindToPage(&Settings{}) } } return nil } func (p *UpdateCheckPage) Render() error { switch p.state { case updateCheckStateChecking: item := p.checkingTextures[p.dotIndex] item.X = p.ScreenCenterX - item.W/2 item.Y = p.ScreenCenterY - item.H/2 p.RenderTexture(p.textShader, item.ID, item.Position(), item.RawDimensions(), item.Color) case updateCheckStateDone: var msg *StringItem if p.hasError { msg = &p.errorMessage } else { msg = &p.doneMessage } msg.X = p.ScreenCenterX - msg.W/2 msg.Y = p.ScreenCenterY + msg.H/2 p.RenderTexture(p.textShader, msg.ID, msg.Position(), msg.RawDimensions(), msg.Color) p.doneHint.X = p.ScreenCenterX - p.doneHint.W/2 p.doneHint.Y = msg.Y - p.doneHint.H - 20 p.RenderTexture(p.textShader, p.doneHint.ID, p.doneHint.Position(), p.doneHint.RawDimensions(), p.doneHint.Color) } return nil } func (p *UpdateCheckPage) Destroy() error { for i := range p.checkingTextures { gl.DeleteTextures(1, &p.checkingTextures[i].ID) } gl.DeleteTextures(1, &p.doneMessage.ID) gl.DeleteTextures(1, &p.doneHint.ID) if p.hasError { gl.DeleteTextures(1, &p.errorMessage.ID) } return nil }