113 lines
3.4 KiB
Go
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)
|
|
}
|