// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package preferences /* #include #include "native.h" */ import "C" import ( "fmt" "strings" "unsafe" "git.stormux.org/storm/cthulhu/internal/config" ) func cloneWineSettings(wine config.WineSettings) config.WineSettings { wine.ExcludedPrograms = append([]string(nil), wine.ExcludedPrograms...) return wine } // validateWinePrograms reports the first entry the Wine tab cannot accept, // naming it so the user can fix or delete exactly that entry. Config // validation remains the backstop for these rules. func validateWinePrograms(programs []string) error { if len(programs) > 256 { return fmt.Errorf("Wine program \"%s\" is entry %d. At most 256 programs can be listed; delete an entry.", programs[256], 257) } for index, name := range programs { switch { case strings.TrimSpace(name) == "": return fmt.Errorf("Wine program entry %d is blank. Type an executable name such as Game.exe, or delete the entry.", index+1) case strings.ContainsAny(name, `/\`): return fmt.Errorf("Wine program \"%s\" is a path. Type only the executable name, such as Game.exe, or delete the entry.", name) case len(name) > 255: return fmt.Errorf("Wine program \"%s\" is too long. Executable names may be at most 255 characters; fix or delete the entry.", name) case !config.ValidWineProgramName(name): return fmt.Errorf("Wine program \"%s\" is not a valid executable name. Remove the surrounding spaces or control characters, or delete the entry.", name) } } return nil } // collapseWinePrograms keeps the first spelling of each program and drops // later case-insensitive duplicates. An empty list stays nil so merged // settings keep their existing shape. func collapseWinePrograms(programs []string) []string { var collapsed []string seen := make(map[string]bool, len(programs)) for _, name := range programs { key := strings.ToLower(name) if seen[key] { continue } seen[key] = true collapsed = append(collapsed, name) } return collapsed } func normalizedWineSettings(wine config.WineSettings) (config.WineSettings, error) { if err := validateWinePrograms(wine.ExcludedPrograms); err != nil { return config.WineSettings{}, err } wine.ExcludedPrograms = collapseWinePrograms(wine.ExcludedPrograms) return wine, nil } func setWineValidation(message string) { text := C.CString(message) defer C.free(unsafe.Pointer(text)) C.prefs_wine_validation(text) } func setWineValues(wine config.WineSettings) { setCheck("wineControllerOnlyCheckButton", wine.ControllerOnly) C.prefs_wine_clear() for _, program := range wine.ExcludedPrograms { name := C.CString(program) C.prefs_wine_append(name) C.free(unsafe.Pointer(name)) } setWineValidation("") } func getWinePrograms() []string { var programs []string for row := 0; row < int(C.prefs_wine_count()); row++ { name := C.prefs_wine_value(C.int(row)) programs = append(programs, C.GoString(name)) C.g_free(C.gpointer(name)) } return programs } func (session *uiSession) refreshWineValidation() { message := "" if err := validateWinePrograms(getWinePrograms()); err != nil { message = err.Error() } setWineValidation(message) } func getWineValues() (config.WineSettings, error) { wine := config.WineSettings{ControllerOnly: getCheck("wineControllerOnlyCheckButton"), ExcludedPrograms: getWinePrograms()} return normalizedWineSettings(wine) }