441 lines
12 KiB
Go
441 lines
12 KiB
Go
package utils
|
|
|
|
import (
|
|
"epochsilicon/pkg/log"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
wineRegistrySection = "[Software\\\\Wine\\\\Mac Driver]"
|
|
leftOptionKey = "\"LeftOptionIsAlt\"=\"Y\""
|
|
rightOptionKey = "\"RightOptionIsAlt\"=\"Y\""
|
|
wineLoaderPath = "/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/CrossOver-Hosted Application/wineloader2"
|
|
registryKeyPath = "HKEY_CURRENT_USER\\Software\\Wine\\Mac Driver"
|
|
)
|
|
|
|
// GetWineUserRegPath returns the path to the Wine user.reg file
|
|
func GetWineUserRegPath() (string, error) {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get user home directory: %v", err)
|
|
}
|
|
return filepath.Join(homeDir, ".wine", "user.reg"), nil
|
|
}
|
|
|
|
// queryRegistryValue queries a specific registry value using Wine's reg command
|
|
func queryRegistryValue(winePrefix, valueName string) bool {
|
|
cmd := exec.Command(wineLoaderPath, "reg", "query", registryKeyPath, "/v", valueName)
|
|
cmd.Env = append(os.Environ(), fmt.Sprintf("WINEPREFIX=%s", winePrefix))
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
// Value doesn't exist or other error
|
|
return false
|
|
}
|
|
|
|
// Check if the output contains the value set to "Y"
|
|
outputStr := string(output)
|
|
return strings.Contains(outputStr, valueName) && strings.Contains(outputStr, "Y")
|
|
}
|
|
|
|
func setRegistryValuesOptimized(winePrefix string, enabled bool) error {
|
|
if enabled {
|
|
return addBothRegistryValues(winePrefix)
|
|
} else {
|
|
return deleteBothRegistryValues(winePrefix)
|
|
}
|
|
}
|
|
|
|
func addBothRegistryValues(winePrefix string) error {
|
|
batchContent := fmt.Sprintf(`@echo off
|
|
reg add "%s" /v "LeftOptionIsAlt" /t REG_SZ /d "Y" /f
|
|
reg add "%s" /v "RightOptionIsAlt" /t REG_SZ /d "Y" /f
|
|
`, registryKeyPath, registryKeyPath)
|
|
|
|
// Create temporary batch file
|
|
tempDir := os.TempDir()
|
|
batchFile := filepath.Join(tempDir, "wine_registry_add.bat")
|
|
|
|
if err := os.WriteFile(batchFile, []byte(batchContent), 0644); err != nil {
|
|
return fmt.Errorf("failed to create batch file: %v", err)
|
|
}
|
|
defer os.Remove(batchFile)
|
|
|
|
// Run the batch file with Wine
|
|
cmd := exec.Command(wineLoaderPath, "cmd", "/c", batchFile)
|
|
cmd.Env = append(os.Environ(), fmt.Sprintf("WINEPREFIX=%s", winePrefix))
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("batch registry add failed: %v, output: %s", err, string(output))
|
|
}
|
|
|
|
log.Infof("Successfully enabled Option-as-Alt mapping in Wine registry (optimized)")
|
|
return nil
|
|
}
|
|
|
|
func deleteBothRegistryValues(winePrefix string) error {
|
|
batchContent := fmt.Sprintf(`@echo off
|
|
reg delete "%s" /v "LeftOptionIsAlt" /f 2>nul
|
|
reg delete "%s" /v "RightOptionIsAlt" /f 2>nul
|
|
`, registryKeyPath, registryKeyPath)
|
|
|
|
// Create temporary batch file
|
|
tempDir := os.TempDir()
|
|
batchFile := filepath.Join(tempDir, "wine_registry_delete.bat")
|
|
|
|
if err := os.WriteFile(batchFile, []byte(batchContent), 0644); err != nil {
|
|
return fmt.Errorf("failed to create batch file: %v", err)
|
|
}
|
|
defer os.Remove(batchFile) // Clean up
|
|
|
|
// Run the batch file with Wine
|
|
cmd := exec.Command(wineLoaderPath, "cmd", "/c", batchFile)
|
|
cmd.Env = append(os.Environ(), fmt.Sprintf("WINEPREFIX=%s", winePrefix))
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("batch registry delete failed: %v, output: %s", err, string(output))
|
|
}
|
|
|
|
log.Infof("Successfully disabled Option-as-Alt mapping in Wine registry (optimized)")
|
|
return nil
|
|
}
|
|
|
|
// CheckOptionAsAltEnabled checks if Option keys are remapped as Alt keys in Wine registry
|
|
func CheckOptionAsAltEnabled() bool {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
log.Infof("Failed to get user home directory: %v", err)
|
|
return false
|
|
}
|
|
|
|
winePrefix := filepath.Join(homeDir, ".wine")
|
|
|
|
// Check if CrossOver wine loader exists
|
|
if !PathExists(wineLoaderPath) {
|
|
log.Infof("CrossOver wine loader not found at: %s", wineLoaderPath)
|
|
return false
|
|
}
|
|
|
|
// Query both registry values
|
|
leftEnabled := queryRegistryValue(winePrefix, "LeftOptionIsAlt")
|
|
rightEnabled := queryRegistryValue(winePrefix, "RightOptionIsAlt")
|
|
|
|
return leftEnabled && rightEnabled
|
|
}
|
|
|
|
// CheckOptionAsAltEnabledFast checks status by reading user.reg file directly
|
|
func CheckOptionAsAltEnabledFast() bool {
|
|
regPath, err := GetWineUserRegPath()
|
|
if err != nil {
|
|
log.Infof("Failed to get Wine registry path: %v", err)
|
|
return false
|
|
}
|
|
|
|
if !PathExists(regPath) {
|
|
log.Infof("Wine user.reg file not found at: %s", regPath)
|
|
return false
|
|
}
|
|
|
|
content, err := os.ReadFile(regPath)
|
|
if err != nil {
|
|
log.Infof("Failed to read Wine registry file: %v", err)
|
|
return false
|
|
}
|
|
|
|
contentStr := string(content)
|
|
|
|
// Check for the Mac Driver section in different possible formats
|
|
macDriverSectionFound := strings.Contains(contentStr, wineRegistrySection) ||
|
|
strings.Contains(contentStr, "[SoftwareWineMac Driver]")
|
|
|
|
if macDriverSectionFound {
|
|
// Look for the registry values in the proper format or any format
|
|
leftOptionFound := strings.Contains(contentStr, leftOptionKey) ||
|
|
strings.Contains(contentStr, "\"LeftOptionIsAlt\"=\"Y\"")
|
|
rightOptionFound := strings.Contains(contentStr, rightOptionKey) ||
|
|
strings.Contains(contentStr, "\"RightOptionIsAlt\"=\"Y\"")
|
|
|
|
return leftOptionFound && rightOptionFound
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// SetOptionAsAltEnabled enables or disables Option key remapping in Wine registry
|
|
func SetOptionAsAltEnabled(enabled bool) error {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get user home directory: %v", err)
|
|
}
|
|
|
|
winePrefix := filepath.Join(homeDir, ".wine")
|
|
|
|
// Check if CrossOver wine loader exists
|
|
if !PathExists(wineLoaderPath) {
|
|
return fmt.Errorf("CrossOver wine loader not found at: %s", wineLoaderPath)
|
|
}
|
|
|
|
// Ensure the .wine directory exists
|
|
if err := os.MkdirAll(winePrefix, 0755); err != nil {
|
|
return fmt.Errorf("failed to create .wine directory: %v", err)
|
|
}
|
|
|
|
if enabled {
|
|
return setRegistryValuesOptimized(winePrefix, true)
|
|
} else {
|
|
err := setRegistryValuesOptimized(winePrefix, false)
|
|
if err != nil {
|
|
log.Infof("Wine registry disable failed: %v", err)
|
|
}
|
|
|
|
err2 := setRegistryValuesFast(false)
|
|
if err2 != nil {
|
|
log.Infof("File-based cleanup failed: %v", err2)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return err2
|
|
}
|
|
}
|
|
|
|
// setRegistryValuesFast directly modifies the user.reg file (much faster)
|
|
func setRegistryValuesFast(enabled bool) error {
|
|
regPath, err := GetWineUserRegPath()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get Wine registry path: %v", err)
|
|
}
|
|
|
|
// Ensure the .wine directory exists
|
|
wineDir := filepath.Dir(regPath)
|
|
if err := os.MkdirAll(wineDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create .wine directory: %v", err)
|
|
}
|
|
|
|
var content string
|
|
var lines []string
|
|
|
|
// Read existing content if file exists
|
|
if PathExists(regPath) {
|
|
contentBytes, err := os.ReadFile(regPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read existing registry file: %v", err)
|
|
}
|
|
content = string(contentBytes)
|
|
lines = strings.Split(content, "\n")
|
|
} else {
|
|
// Create basic registry structure if file doesn't exist
|
|
content = "WINE REGISTRY Version 2\n;; All keys relative to \\User\n\n"
|
|
lines = strings.Split(content, "\n")
|
|
}
|
|
|
|
if enabled {
|
|
return addOptionAsAltSettingsFast(regPath, lines)
|
|
} else {
|
|
return removeOptionAsAltSettingsFast(regPath, lines)
|
|
}
|
|
}
|
|
|
|
// addOptionAsAltSettingsFast adds the Option-as-Alt registry settings directly to the file
|
|
func addOptionAsAltSettingsFast(regPath string, lines []string) error {
|
|
var newLines []string
|
|
sectionFound := false
|
|
sectionIndex := -1
|
|
leftOptionFound := false
|
|
rightOptionFound := false
|
|
|
|
// Find the Mac Driver section
|
|
for i, line := range lines {
|
|
if strings.TrimSpace(line) == wineRegistrySection {
|
|
sectionFound = true
|
|
sectionIndex = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if !sectionFound {
|
|
// Add the section at the end
|
|
newLines = append(lines, "")
|
|
newLines = append(newLines, wineRegistrySection)
|
|
newLines = append(newLines, "#time=1dbd859c084de18")
|
|
newLines = append(newLines, leftOptionKey)
|
|
newLines = append(newLines, rightOptionKey)
|
|
} else {
|
|
// Section exists, check if keys are already present
|
|
newLines = make([]string, len(lines))
|
|
copy(newLines, lines)
|
|
|
|
// Look for existing keys in the section
|
|
for i := sectionIndex + 1; i < len(lines); i++ {
|
|
line := strings.TrimSpace(lines[i])
|
|
if line == "" {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(line, "[") && line != wineRegistrySection {
|
|
// Found start of another section, stop looking
|
|
break
|
|
}
|
|
if strings.Contains(line, "LeftOptionIsAlt") {
|
|
leftOptionFound = true
|
|
if !strings.Contains(line, "\"Y\"") {
|
|
newLines[i] = leftOptionKey
|
|
}
|
|
}
|
|
if strings.Contains(line, "RightOptionIsAlt") {
|
|
rightOptionFound = true
|
|
if !strings.Contains(line, "\"Y\"") {
|
|
newLines[i] = rightOptionKey
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add missing keys
|
|
if !leftOptionFound || !rightOptionFound {
|
|
insertIndex := sectionIndex + 1
|
|
|
|
// Add timestamp if it doesn't exist
|
|
timestampExists := false
|
|
for i := sectionIndex + 1; i < len(newLines); i++ {
|
|
if strings.HasPrefix(strings.TrimSpace(newLines[i]), "#time=") {
|
|
timestampExists = true
|
|
break
|
|
}
|
|
if strings.HasPrefix(strings.TrimSpace(newLines[i]), "[") && newLines[i] != wineRegistrySection {
|
|
break
|
|
}
|
|
}
|
|
|
|
if !timestampExists {
|
|
timestampLine := "#time=1dbd859c084de18"
|
|
newLines = insertLine(newLines, insertIndex, timestampLine)
|
|
insertIndex++
|
|
}
|
|
|
|
if !leftOptionFound {
|
|
newLines = insertLine(newLines, insertIndex, leftOptionKey)
|
|
insertIndex++
|
|
}
|
|
if !rightOptionFound {
|
|
newLines = insertLine(newLines, insertIndex, rightOptionKey)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write the updated content
|
|
newContent := strings.Join(newLines, "\n")
|
|
if err := os.WriteFile(regPath, []byte(newContent), 0644); err != nil {
|
|
return fmt.Errorf("failed to write registry file: %v", err)
|
|
}
|
|
|
|
log.Infof("Successfully enabled Option-as-Alt mapping in Wine registry (fast method)")
|
|
return nil
|
|
}
|
|
|
|
// removeOptionAsAltSettingsFast removes the Option-as-Alt registry settings directly from the file
|
|
func removeOptionAsAltSettingsFast(regPath string, lines []string) error {
|
|
if !PathExists(regPath) {
|
|
// File doesn't exist, nothing to remove
|
|
log.Infof("Successfully disabled Option-as-Alt mapping in Wine registry (no file to modify)")
|
|
return nil
|
|
}
|
|
|
|
var newLines []string
|
|
|
|
// Remove lines that contain our option key settings from any section
|
|
for _, line := range lines {
|
|
trimmedLine := strings.TrimSpace(line)
|
|
|
|
// Skip lines that contain our option key settings
|
|
if strings.Contains(trimmedLine, "LeftOptionIsAlt") || strings.Contains(trimmedLine, "RightOptionIsAlt") {
|
|
continue
|
|
}
|
|
|
|
newLines = append(newLines, line)
|
|
}
|
|
|
|
// Check if any Mac Driver sections are now empty and remove them
|
|
newLines = removeEmptyMacDriverSections(newLines)
|
|
|
|
// Write the updated content
|
|
newContent := strings.Join(newLines, "\n")
|
|
if err := os.WriteFile(regPath, []byte(newContent), 0644); err != nil {
|
|
return fmt.Errorf("failed to write registry file: %v", err)
|
|
}
|
|
|
|
log.Infof("Successfully disabled Option-as-Alt mapping in Wine registry (fast method)")
|
|
return nil
|
|
}
|
|
|
|
// removeEmptyMacDriverSections removes empty Mac Driver sections from the registry
|
|
func removeEmptyMacDriverSections(lines []string) []string {
|
|
var finalLines []string
|
|
i := 0
|
|
|
|
for i < len(lines) {
|
|
line := strings.TrimSpace(lines[i])
|
|
|
|
// Check if this is a Mac Driver section
|
|
if line == wineRegistrySection || line == "[SoftwareWineMac Driver]" {
|
|
// Check if the section is empty (only contains timestamp or nothing)
|
|
sectionStart := i
|
|
sectionEnd := i + 1
|
|
sectionEmpty := true
|
|
|
|
// Find the end of this section
|
|
for sectionEnd < len(lines) {
|
|
nextLine := strings.TrimSpace(lines[sectionEnd])
|
|
if nextLine == "" {
|
|
sectionEnd++
|
|
continue
|
|
}
|
|
if strings.HasPrefix(nextLine, "[") {
|
|
// Start of new section
|
|
break
|
|
}
|
|
if !strings.HasPrefix(nextLine, "#time=") {
|
|
// Found non-timestamp content in section
|
|
sectionEmpty = false
|
|
}
|
|
sectionEnd++
|
|
}
|
|
|
|
if sectionEmpty {
|
|
// Skip the entire empty section
|
|
i = sectionEnd
|
|
continue
|
|
} else {
|
|
// Keep the section
|
|
for j := sectionStart; j < sectionEnd; j++ {
|
|
finalLines = append(finalLines, lines[j])
|
|
}
|
|
i = sectionEnd
|
|
continue
|
|
}
|
|
}
|
|
|
|
finalLines = append(finalLines, lines[i])
|
|
i++
|
|
}
|
|
|
|
return finalLines
|
|
}
|
|
|
|
// insertLine inserts a line at the specified index
|
|
func insertLine(lines []string, index int, newLine string) []string {
|
|
if index >= len(lines) {
|
|
return append(lines, newLine)
|
|
}
|
|
|
|
lines = append(lines, "")
|
|
copy(lines[index+1:], lines[index:])
|
|
lines[index] = newLine
|
|
return lines
|
|
}
|