added option key remap
This commit is contained in:
440
pkg/utils/wine_registry.go
Normal file
440
pkg/utils/wine_registry.go
Normal file
@@ -0,0 +1,440 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"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.Printf("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.Printf("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.Printf("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.Printf("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.Printf("Failed to get Wine registry path: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if !PathExists(regPath) {
|
||||
log.Printf("Wine user.reg file not found at: %s", regPath)
|
||||
return false
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(regPath)
|
||||
if err != nil {
|
||||
log.Printf("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.Printf("Wine registry disable failed: %v", err)
|
||||
}
|
||||
|
||||
err2 := setRegistryValuesFast(false)
|
||||
if err2 != nil {
|
||||
log.Printf("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.Printf("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.Printf("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.Printf("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
|
||||
}
|
Reference in New Issue
Block a user