package ui import ( "epochsilicon/pkg/log" "git.burkey.co/eburk/epochcli/pkg/epoch" "os" "path/filepath" "strings" "time" "epochsilicon/pkg/paths" "epochsilicon/pkg/service" "epochsilicon/pkg/utils" "fyne.io/fyne/v2" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) var ( pulsingActive = false ) // UpdateAllStatuses updates all UI components based on current application state func UpdateAllStatuses() { updateCrossoverStatus() updateEpochStatus() updatePlayButtonState() updateServiceStatus() // Update Wine registry status if components are initialized if optionAsAltStatusLabel != nil { updateWineRegistryStatus() } // Update recommended settings button if component is initialized if applyRecommendedSettingsButton != nil { updateRecommendedSettingsButton() } } // updateCrossoverStatus updates CrossOver path and patch status func updateCrossoverStatus() { if paths.CrossoverPath == "" { crossoverPathLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Not set", Style: widget.RichTextStyle{ColorName: theme.ColorNameError}}} paths.PatchesAppliedCrossOver = false // Reset if path is cleared } else { crossoverPathLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: paths.CrossoverPath, Style: widget.RichTextStyle{ColorName: theme.ColorNameSuccess}}} wineloader2Path := filepath.Join(paths.CrossoverPath, "Contents", "SharedSupport", "CrossOver", "CrossOver-Hosted Application", "wineloader2") if utils.PathExists(wineloader2Path) { paths.PatchesAppliedCrossOver = true } } crossoverPathLabel.Refresh() if paths.PatchesAppliedCrossOver { crossoverStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Patched", Style: widget.RichTextStyle{ColorName: theme.ColorNameSuccess}}} if patchCrossOverButton != nil { patchCrossOverButton.Disable() } if unpatchCrossOverButton != nil { unpatchCrossOverButton.Enable() } } else { crossoverStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Not patched", Style: widget.RichTextStyle{ColorName: theme.ColorNameError}}} if patchCrossOverButton != nil { if paths.CrossoverPath != "" { patchCrossOverButton.Enable() } else { patchCrossOverButton.Disable() } } if unpatchCrossOverButton != nil { unpatchCrossOverButton.Disable() } } crossoverStatusLabel.Refresh() } // updateEpochStatus updates Epoch path and patch status func updateEpochStatus() { if paths.EpochPath == "" { epochPathLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Not set", Style: widget.RichTextStyle{ColorName: theme.ColorNameError}}} paths.PatchesAppliedEpoch = false // Reset if path is cleared } else { epochPathLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: paths.EpochPath, Style: widget.RichTextStyle{ColorName: theme.ColorNameSuccess}}} // Check if all required files exist winerosettaDllPath := filepath.Join(paths.EpochPath, "winerosetta.dll") d3d9DllPath := filepath.Join(paths.EpochPath, "d3d9.dll") rosettaX87DirPath := filepath.Join(paths.EpochPath, "rosettax87") dllsTextFile := filepath.Join(paths.EpochPath, "dlls.txt") rosettaX87ExePath := filepath.Join(rosettaX87DirPath, "rosettax87") libRuntimeRosettaX87Path := filepath.Join(rosettaX87DirPath, "libRuntimeRosettax87") dllsFileValid := false if utils.PathExists(dllsTextFile) { if fileContent, err := os.ReadFile(dllsTextFile); err == nil { contentStr := string(fileContent) winerosettaPresent := strings.Contains(contentStr, "winerosetta.dll") // Validate dlls.txt: winerosetta must be present if winerosettaPresent { dllsFileValid = true } } } // Check for Epoch-specific files epochPatchesApplied := false stats, err := epoch.Update(paths.EpochPath, false, true, true) if err != nil { log.Debugf("Failed to get download Epoch patches: %v", err) } if stats.Outdated == 0 { log.Debug("Nothing is outdated") epochPatchesApplied = true } // Check if patched files have the correct size (matches bundled versions) winerosettaDllCorrectSize := utils.CompareFileWithBundledResource(winerosettaDllPath, "winerosetta/winerosetta.dll") d3d9DllCorrectSize := utils.CompareFileWithBundledResource(d3d9DllPath, "winerosetta/d3d9.dll") rosettaX87CorrectSize := utils.CompareFileWithBundledResource(rosettaX87ExePath, "rosettax87/rosettax87") libRuntimeRosettaX87CorrectSize := utils.CompareFileWithBundledResource(libRuntimeRosettaX87Path, "rosettax87/libRuntimeRosettax87") if utils.PathExists(winerosettaDllPath) && utils.PathExists(d3d9DllPath) && utils.DirExists(rosettaX87DirPath) && utils.PathExists(rosettaX87ExePath) && utils.PathExists(libRuntimeRosettaX87Path) && dllsFileValid && winerosettaDllCorrectSize && d3d9DllCorrectSize && rosettaX87CorrectSize && libRuntimeRosettaX87CorrectSize && epochPatchesApplied { paths.PatchesAppliedEpoch = true } } epochPathLabel.Refresh() if paths.DownloadingPatches { epochStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Downloading...", Style: widget.RichTextStyle{ColorName: theme.ColorNamePrimary}}} if patchEpochButton != nil { patchEpochButton.Disable() } if unpatchEpochButton != nil { unpatchEpochButton.Disable() } } else if paths.PatchesAppliedEpoch { epochStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Patched", Style: widget.RichTextStyle{ColorName: theme.ColorNameSuccess}}} if patchEpochButton != nil { patchEpochButton.Disable() } if unpatchEpochButton != nil { unpatchEpochButton.Enable() } } else { epochStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Not patched", Style: widget.RichTextStyle{ColorName: theme.ColorNameError}}} if patchEpochButton != nil { if paths.EpochPath != "" { patchEpochButton.Enable() } else { patchEpochButton.Disable() } } if unpatchEpochButton != nil { unpatchEpochButton.Disable() } } epochStatusLabel.Refresh() } // updatePlayButtonState enables/disables play and launch buttons based on current state func updatePlayButtonState() { launchEnabled := paths.PatchesAppliedEpoch && paths.PatchesAppliedCrossOver && paths.EpochPath != "" && paths.CrossoverPath != "" && service.IsServiceRunning() if launchButton != nil { if launchEnabled { launchButton.Enable() } else { launchButton.Disable() } } if playButton != nil && playButtonText != nil { if launchEnabled { playButton.Enable() // Update text to show enabled state with white color playButtonText.Segments = []widget.RichTextSegment{ &widget.TextSegment{ Text: "PLAY", Style: widget.RichTextStyle{ SizeName: theme.SizeNameHeadingText, ColorName: theme.ColorNameForegroundOnPrimary, }, }, } } else { playButton.Disable() // Update text to show disabled state with dimmed color and different text playButtonText.Segments = []widget.RichTextSegment{ &widget.TextSegment{ Text: "PLAY", Style: widget.RichTextStyle{ SizeName: theme.SizeNameHeadingText, ColorName: theme.ColorNameDisabled, }, }, } } playButtonText.Refresh() } } // updateServiceStatus updates RosettaX87 service status and related buttons func updateServiceStatus() { if paths.ServiceStarting { // Show pulsing "Starting..." when service is starting if serviceStatusLabel != nil { if !pulsingActive { pulsingActive = true go startPulsingAnimation() } } if startServiceButton != nil { startServiceButton.Disable() } if stopServiceButton != nil { stopServiceButton.Disable() } } else if service.IsServiceRunning() { pulsingActive = false paths.RosettaX87ServiceRunning = true if serviceStatusLabel != nil { serviceStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Running", Style: widget.RichTextStyle{ColorName: theme.ColorNameSuccess}}} serviceStatusLabel.Refresh() } if startServiceButton != nil { startServiceButton.Disable() } if stopServiceButton != nil { stopServiceButton.Enable() } } else { pulsingActive = false paths.RosettaX87ServiceRunning = false if serviceStatusLabel != nil { serviceStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Stopped", Style: widget.RichTextStyle{ColorName: theme.ColorNameError}}} serviceStatusLabel.Refresh() } if startServiceButton != nil { if paths.EpochPath != "" && paths.PatchesAppliedEpoch { startServiceButton.Enable() } else { startServiceButton.Disable() } } if stopServiceButton != nil { stopServiceButton.Disable() } } } // startPulsingAnimation creates a pulsing effect for the "Starting..." text func startPulsingAnimation() { dots := 0 for pulsingActive && paths.ServiceStarting { var text string switch dots % 4 { case 0: text = "Starting" case 1: text = "Starting." case 2: text = "Starting.." case 3: text = "Starting..." } if serviceStatusLabel != nil { fyne.DoAndWait(func() { serviceStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: text, Style: widget.RichTextStyle{ColorName: theme.ColorNamePrimary}}} serviceStatusLabel.Refresh() }) } time.Sleep(500 * time.Millisecond) dots++ } }