Files
2026-11-02 23:37:45 -05:00

113 lines
3.4 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package preferences
/*
#include <stdlib.h>
#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)
}