diff --git a/internal/gui/gui.go b/internal/gui/gui.go index 0418832..86763d2 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -15,6 +15,7 @@ import ( "fmt" "os" "os/exec" + "strings" "sync" "time" @@ -231,6 +232,13 @@ func formPhase(cfg config.Config, runCfg runner.RunConfig, cfgPath string) formR // Phase 2 – Live run view // -------------------------------------------------------------------------- +// stepIndicator tracks a step's label and blink state. +type stepIndicator struct { + label *walk.Label + stepID string + status string // "pending", "running", "ok", "error", "skipped", "cancelled" +} + func runPhase(runCfg runner.RunConfig, steps []runner.Step) []runner.Result { var ( mw *walk.MainWindow @@ -239,6 +247,21 @@ func runPhase(runCfg runner.RunConfig, steps []runner.Step) []runner.Result { cancelFn context.CancelFunc ) + // Build step indicator labels (one per step, in order) + indicators := make([]stepIndicator, len(steps)) + stepIndex := make(map[string]int, len(steps)) // stepID → index + indWidgets := make([]Widget, len(steps)) + for i, s := range steps { + indicators[i] = stepIndicator{stepID: s.ID, status: "pending"} + stepIndex[s.ID] = i + indWidgets[i] = Label{ + AssignTo: &indicators[i].label, + Text: s.Num + " \u00b7", + Font: Font{Family: "Consolas", PointSize: 9}, + MinSize: Size{Width: 38}, + } + } + if err := (MainWindow{ AssignTo: &mw, Title: "xetup \u2014 probiha instalace", @@ -246,13 +269,18 @@ func runPhase(runCfg runner.RunConfig, steps []runner.Step) []runner.Result { Layout: VBox{}, Children: []Widget{ Label{AssignTo: &statusLbl, Text: "Spoustim..."}, + // Step progress strip + Composite{ + Layout: HBox{MarginsZero: true}, + Children: indWidgets, + }, HSeparator{}, TextEdit{ AssignTo: &logTE, ReadOnly: true, VScroll: true, Font: Font{Family: "Consolas", PointSize: 9}, - MinSize: Size{Height: 590}, + MinSize: Size{Height: 530}, }, HSeparator{}, Composite{ @@ -274,6 +302,29 @@ func runPhase(runCfg runner.RunConfig, steps []runner.Step) []runner.Result { return nil } + // updateIndicator refreshes label text and color — call inside Synchronize. + updateIndicator := func(idx int) { + ind := &indicators[idx] + s := steps[idx] + switch ind.status { + case "running": + ind.label.SetText(s.Num + " \u25ba") // ► + ind.label.SetTextColor(walk.RGB(30, 144, 255)) // dodger blue + case "ok": + ind.label.SetText(s.Num + " \u2713") // ✓ + ind.label.SetTextColor(walk.RGB(0, 180, 0)) + case "error": + ind.label.SetText(s.Num + " \u2717") // ✗ + ind.label.SetTextColor(walk.RGB(220, 50, 50)) + case "skipped", "cancelled": + ind.label.SetText(s.Num + " \u2013") // – + ind.label.SetTextColor(walk.RGB(140, 140, 140)) + default: + ind.label.SetText(s.Num + " \u00b7") // · + ind.label.SetTextColor(walk.RGB(180, 180, 180)) + } + } + var ( mu sync.Mutex results []runner.Result @@ -283,7 +334,6 @@ func runPhase(runCfg runner.RunConfig, steps []runner.Step) []runner.Result { ctx, cancel := context.WithCancel(context.Background()) cancelFn = cancel - // Cancel running scripts when user closes the window. mw.Closing().Attach(func(_ *bool, _ walk.CloseReason) { cancelFn() }) @@ -295,11 +345,26 @@ func runPhase(runCfg runner.RunConfig, steps []runner.Step) []runner.Result { logTE.AppendText(l.Text + "\r\n") }) }, + func(step runner.Step) { + mw.Synchronize(func() { + statusLbl.SetText(fmt.Sprintf( + "Krok %s \u2013 %s...", step.Num, step.Name, + )) + if idx, ok := stepIndex[step.ID]; ok { + indicators[idx].status = "running" + updateIndicator(idx) + } + }) + }, func(res runner.Result) { mw.Synchronize(func() { statusLbl.SetText(fmt.Sprintf( "Krok %s \u2013 %s: %s", res.Step.Num, res.Step.Name, res.Status, )) + if idx, ok := stepIndex[res.Step.ID]; ok { + indicators[idx].status = strings.ToLower(res.Status) + updateIndicator(idx) + } }) }, ) @@ -316,8 +381,8 @@ func runPhase(runCfg runner.RunConfig, steps []runner.Step) []runner.Result { }() mw.Run() - cancel() // stop scripts if window was closed by user - <-done // wait for goroutine to exit cleanly + cancel() + <-done mu.Lock() defer mu.Unlock() diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 4bec1d8..950bf5c 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -141,15 +141,17 @@ type LogLine struct { // Runner executes deployment steps sequentially. type Runner struct { - cfg RunConfig - onLog func(LogLine) - onResult func(Result) - cancel context.CancelFunc + cfg RunConfig + onLog func(LogLine) + onStepStart func(Step) + onResult func(Result) + cancel context.CancelFunc } // New creates a Runner. onLog is called for each output line, onResult after each step. -func New(cfg RunConfig, onLog func(LogLine), onResult func(Result)) *Runner { - return &Runner{cfg: cfg, onLog: onLog, onResult: onResult} +// onStepStart (optional) is called immediately before a step's script is launched. +func New(cfg RunConfig, onLog func(LogLine), onStepStart func(Step), onResult func(Result)) *Runner { + return &Runner{cfg: cfg, onLog: onLog, onStepStart: onStepStart, onResult: onResult} } // Run executes enabled steps sequentially. Blocks until done or context cancelled. @@ -170,6 +172,9 @@ func (r *Runner) Run(ctx context.Context, steps []Step) []Result { continue } + if r.onStepStart != nil { + r.onStepStart(step) + } start := time.Now() err := r.runScript(ctx, step, cfgArg) elapsed := time.Since(start) diff --git a/scripts/03-system-registry.ps1 b/scripts/03-system-registry.ps1 index f62d5aa..93b7176 100644 --- a/scripts/03-system-registry.ps1 +++ b/scripts/03-system-registry.ps1 @@ -316,8 +316,11 @@ if (Get-Feature $Config "systemRegistry" "edgePolicies") { Set-Reg -Path $edgePath -Name "HideFirstRunExperience" -Value 1 Set-Reg -Path $edgePath -Name "DefaultBrowserSettingEnabled" -Value 0 - # New tab page / recommendations - Set-Reg -Path $edgePath -Name "NewTabPageContentEnabled" -Value 0 + # New tab page - disable all visual clutter + Set-Reg -Path $edgePath -Name "NewTabPageContentEnabled" -Value 0 # feed / obsah + Set-Reg -Path $edgePath -Name "NewTabPageQuickLinksEnabled" -Value 0 # rychle odkazy + Set-Reg -Path $edgePath -Name "NewTabPageBackgroundEnabled" -Value 0 # pozadi + Set-Reg -Path $edgePath -Name "NewTabPageAllowedBackgroundTypes" -Value 3 # 3 = only solid color Set-Reg -Path $edgePath -Name "ShowRecommendationsEnabled" -Value 0 Set-Reg -Path $edgePath -Name "SpotlightExperiencesAndRecommendationsEnabled" -Value 0 Set-Reg -Path $edgePath -Name "PersonalizationReportingEnabled" -Value 0 diff --git a/scripts/04-default-profile.ps1 b/scripts/04-default-profile.ps1 index 40d8eee..b963bb9 100644 --- a/scripts/04-default-profile.ps1 +++ b/scripts/04-default-profile.ps1 @@ -242,7 +242,12 @@ try { '@ } - default { "" } # empty = clean slate + default { +@' + + +'@ + } # explicit pins with Replace = no Store, no other defaults } $taskbarLayoutXml = @" diff --git a/scripts/05-personalization.ps1 b/scripts/05-personalization.ps1 index 16b79c8..987f5d0 100644 --- a/scripts/05-personalization.ps1 +++ b/scripts/05-personalization.ps1 @@ -101,6 +101,12 @@ function Apply-ThemeSettings { Set-Reg -Path "$HiveRoot\Control Panel\Colors" ` -Name "Background" -Value "34 59 71" -Type "String" + # Empty Wallpaper path = solid color from Background key above. + # Without this, new users created from Default hive inherit a broken/missing + # wallpaper path and Windows falls back to black desktop. + Set-Reg -Path "$HiveRoot\Control Panel\Desktop" ` + -Name "Wallpaper" -Value "" -Type "String" + Set-Reg -Path "$HiveRoot\Control Panel\Desktop" ` -Name "WallpaperStyle" -Value "0" -Type "String"