added option key remap
This commit is contained in:
		
							
								
								
									
										10
									
								
								main_test.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								main_test.go
									
									
									
									
									
								
							| @@ -1,10 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestMain(t *testing.T) { | ||||
| 	// Basic test to ensure the main package can be imported and tested | ||||
| 	t.Log("TurtleSilicon main package test passed") | ||||
| } | ||||
| @@ -3,6 +3,8 @@ package ui | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"turtlesilicon/pkg/launcher" | ||||
| 	"turtlesilicon/pkg/patching" | ||||
| @@ -45,6 +47,9 @@ func createOptionsComponents() { | ||||
| 	vanillaTweaksCheckbox.SetChecked(prefs.EnableVanillaTweaks) | ||||
| 	launcher.EnableVanillaTweaks = prefs.EnableVanillaTweaks | ||||
|  | ||||
| 	// Create Wine registry Option-as-Alt buttons and status | ||||
| 	createWineRegistryComponents() | ||||
|  | ||||
| 	// Load environment variables from preferences | ||||
| 	if prefs.EnvironmentVariables != "" { | ||||
| 		launcher.CustomEnvVars = prefs.EnvironmentVariables | ||||
| @@ -158,3 +163,182 @@ func createBottomBar(myWindow fyne.Window) fyne.CanvasObject { | ||||
|  | ||||
| 	return container.NewPadded(bottomContainer) | ||||
| } | ||||
|  | ||||
| // createWineRegistryComponents creates Wine registry Option-as-Alt buttons and status | ||||
| func createWineRegistryComponents() { | ||||
| 	// Create status label to show current state | ||||
| 	optionAsAltStatusLabel = widget.NewRichText() | ||||
|  | ||||
| 	// Create enable button | ||||
| 	enableOptionAsAltButton = widget.NewButton("Enable", func() { | ||||
| 		enableOptionAsAltButton.Disable() | ||||
| 		disableOptionAsAltButton.Disable() | ||||
|  | ||||
| 		// Show loading state in status label | ||||
| 		fyne.Do(func() { | ||||
| 			optionAsAltStatusLabel.ParseMarkdown("**Remap Option key as Alt key:** Enabling...") | ||||
| 			startPulsingEffect() | ||||
| 		}) | ||||
|  | ||||
| 		// Run in goroutine to avoid blocking UI | ||||
| 		go func() { | ||||
| 			if err := utils.SetOptionAsAltEnabled(true); err != nil { | ||||
| 				log.Printf("Failed to enable Option-as-Alt mapping: %v", err) | ||||
| 				// Update UI on main thread | ||||
| 				fyne.Do(func() { | ||||
| 					stopPulsingEffect() | ||||
| 					optionAsAltStatusLabel.ParseMarkdown("**Remap Option key as Alt key:** Enable Failed") | ||||
| 				}) | ||||
| 				time.Sleep(2 * time.Second) // Show error briefly | ||||
| 			} else { | ||||
| 				log.Printf("Successfully enabled Option-as-Alt mapping") | ||||
| 				// Update preferences | ||||
| 				prefs, _ := utils.LoadPrefs() | ||||
| 				prefs.RemapOptionAsAlt = true | ||||
| 				utils.SavePrefs(prefs) | ||||
| 			} | ||||
|  | ||||
| 			// Update UI on main thread | ||||
| 			fyne.Do(func() { | ||||
| 				stopPulsingEffect() | ||||
| 				updateWineRegistryStatusWithMethod(true) // Use Wine command for accurate check after modifications | ||||
| 			}) | ||||
| 		}() | ||||
| 	}) | ||||
|  | ||||
| 	// Create disable button | ||||
| 	disableOptionAsAltButton = widget.NewButton("Disable", func() { | ||||
| 		enableOptionAsAltButton.Disable() | ||||
| 		disableOptionAsAltButton.Disable() | ||||
|  | ||||
| 		// Show loading state in status label | ||||
| 		fyne.Do(func() { | ||||
| 			optionAsAltStatusLabel.ParseMarkdown("**Remap Option key as Alt key:** Disabling...") | ||||
| 			startPulsingEffect() | ||||
| 		}) | ||||
|  | ||||
| 		// Run in goroutine to avoid blocking UI | ||||
| 		go func() { | ||||
| 			if err := utils.SetOptionAsAltEnabled(false); err != nil { | ||||
| 				log.Printf("Failed to disable Option-as-Alt mapping: %v", err) | ||||
| 				// Update UI on main thread | ||||
| 				fyne.Do(func() { | ||||
| 					stopPulsingEffect() | ||||
| 					optionAsAltStatusLabel.ParseMarkdown("**Remap Option key as Alt key:** Disable Failed") | ||||
| 				}) | ||||
| 				time.Sleep(2 * time.Second) // Show error briefly | ||||
| 			} else { | ||||
| 				log.Printf("Successfully disabled Option-as-Alt mapping") | ||||
| 				// Update preferences | ||||
| 				prefs, _ := utils.LoadPrefs() | ||||
| 				prefs.RemapOptionAsAlt = false | ||||
| 				utils.SavePrefs(prefs) | ||||
| 			} | ||||
|  | ||||
| 			// Update UI on main thread | ||||
| 			fyne.Do(func() { | ||||
| 				stopPulsingEffect() | ||||
| 				updateWineRegistryStatusWithMethod(true) // Use Wine command for accurate check after modifications | ||||
| 			}) | ||||
| 		}() | ||||
| 	}) | ||||
|  | ||||
| 	// Style the buttons similar to other action buttons | ||||
| 	enableOptionAsAltButton.Importance = widget.MediumImportance | ||||
| 	disableOptionAsAltButton.Importance = widget.MediumImportance | ||||
|  | ||||
| 	// Initialize status and button states | ||||
| 	updateWineRegistryStatus() | ||||
| } | ||||
|  | ||||
| // updateWineRegistryStatus updates the Wine registry status label and button states | ||||
| func updateWineRegistryStatus() { | ||||
| 	updateWineRegistryStatusWithMethod(false) | ||||
| } | ||||
|  | ||||
| // updateWineRegistryStatusWithMethod updates status with choice of checking method | ||||
| func updateWineRegistryStatusWithMethod(useWineCommand bool) { | ||||
| 	if useWineCommand { | ||||
| 		// Use Wine command for accurate check after modifications | ||||
| 		currentWineRegistryEnabled = utils.CheckOptionAsAltEnabled() | ||||
| 	} else { | ||||
| 		// Use fast file-based check for regular status updates | ||||
| 		currentWineRegistryEnabled = utils.CheckOptionAsAltEnabledFast() | ||||
| 	} | ||||
|  | ||||
| 	// Update UI with simple white text | ||||
| 	if currentWineRegistryEnabled { | ||||
| 		optionAsAltStatusLabel.ParseMarkdown("**Remap Option key as Alt key:** Enabled") | ||||
| 	} else { | ||||
| 		optionAsAltStatusLabel.ParseMarkdown("**Remap Option key as Alt key:** Disabled") | ||||
| 	} | ||||
|  | ||||
| 	// Update button states based on current status | ||||
| 	if currentWineRegistryEnabled { | ||||
| 		// If enabled, only show disable button as clickable | ||||
| 		enableOptionAsAltButton.Disable() | ||||
| 		disableOptionAsAltButton.Enable() | ||||
| 	} else { | ||||
| 		// If disabled, only show enable button as clickable | ||||
| 		enableOptionAsAltButton.Enable() | ||||
| 		disableOptionAsAltButton.Disable() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // startPulsingEffect starts a pulsing animation for the status label during loading | ||||
| func startPulsingEffect() { | ||||
| 	if pulsingActive { | ||||
| 		return // Already pulsing | ||||
| 	} | ||||
|  | ||||
| 	pulsingActive = true | ||||
| 	pulsingTicker = time.NewTicker(500 * time.Millisecond) | ||||
|  | ||||
| 	go func() { | ||||
| 		dots := "" | ||||
|  | ||||
| 		for pulsingActive { | ||||
| 			select { | ||||
| 			case <-pulsingTicker.C: | ||||
| 				if pulsingActive { | ||||
| 					// Cycle through different dot patterns for visual effect | ||||
| 					switch len(dots) { | ||||
| 					case 0: | ||||
| 						dots = "." | ||||
| 					case 1: | ||||
| 						dots = ".." | ||||
| 					case 2: | ||||
| 						dots = "..." | ||||
| 					default: | ||||
| 						dots = "" | ||||
| 					} | ||||
|  | ||||
| 					// Update the label with pulsing dots | ||||
| 					fyne.Do(func() { | ||||
| 						if pulsingActive && optionAsAltStatusLabel != nil { | ||||
| 							// Use the dots directly in the status text | ||||
| 							if strings.Contains(optionAsAltStatusLabel.String(), "Enabling") { | ||||
| 								optionAsAltStatusLabel.ParseMarkdown("**Remap Option key as Alt key:** Enabling" + dots) | ||||
| 							} else if strings.Contains(optionAsAltStatusLabel.String(), "Disabling") { | ||||
| 								optionAsAltStatusLabel.ParseMarkdown("**Remap Option key as Alt key:** Disabling" + dots) | ||||
| 							} | ||||
| 						} | ||||
| 					}) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
|  | ||||
| // stopPulsingEffect stops the pulsing animation | ||||
| func stopPulsingEffect() { | ||||
| 	if !pulsingActive { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pulsingActive = false | ||||
| 	if pulsingTicker != nil { | ||||
| 		pulsingTicker.Stop() | ||||
| 		pulsingTicker = nil | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -21,6 +21,8 @@ func showOptionsPopup() { | ||||
| 		metalHudCheckbox, | ||||
| 		showTerminalCheckbox, | ||||
| 		vanillaTweaksCheckbox, | ||||
| 		widget.NewSeparator(), | ||||
| 		container.NewBorder(nil, nil, nil, container.NewHBox(enableOptionAsAltButton, disableOptionAsAltButton), optionAsAltStatusLabel), | ||||
| 	) | ||||
|  | ||||
| 	envVarsTitle := widget.NewLabel("Environment Variables") | ||||
| @@ -55,8 +57,8 @@ func showOptionsPopup() { | ||||
|  | ||||
| 	// Get the window size and calculate 2/3 size | ||||
| 	windowSize := currentWindow.Content().Size() | ||||
| 	popupWidth := windowSize.Width * 2 / 3 | ||||
| 	popupHeight := windowSize.Height * 2 / 3 | ||||
| 	popupWidth := windowSize.Width * 5 / 6 | ||||
| 	popupHeight := windowSize.Height * 5 / 6 | ||||
|  | ||||
| 	// Create a modal popup | ||||
| 	popup := widget.NewModalPopUp(popupContent, currentWindow.Canvas()) | ||||
|   | ||||
| @@ -25,6 +25,11 @@ func UpdateAllStatuses() { | ||||
| 	updateTurtleWoWStatus() | ||||
| 	updatePlayButtonState() | ||||
| 	updateServiceStatus() | ||||
|  | ||||
| 	// Update Wine registry status if components are initialized | ||||
| 	if optionAsAltStatusLabel != nil { | ||||
| 		updateWineRegistryStatus() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // updateCrossoverStatus updates CrossOver path and patch status | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| package ui | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"fyne.io/fyne/v2" | ||||
| 	"fyne.io/fyne/v2/widget" | ||||
| ) | ||||
| @@ -30,9 +32,20 @@ var ( | ||||
| 	showTerminalCheckbox  *widget.Check | ||||
| 	vanillaTweaksCheckbox *widget.Check | ||||
|  | ||||
| 	// Wine registry buttons and status | ||||
| 	enableOptionAsAltButton  *widget.Button | ||||
| 	disableOptionAsAltButton *widget.Button | ||||
| 	optionAsAltStatusLabel   *widget.RichText | ||||
|  | ||||
| 	// Environment variables entry | ||||
| 	envVarsEntry *widget.Entry | ||||
|  | ||||
| 	// Window reference for popup functionality | ||||
| 	currentWindow fyne.Window | ||||
|  | ||||
| 	// State variables | ||||
| 	currentWineRegistryEnabled bool | ||||
|  | ||||
| 	// Pulsing effect variables (pulsingActive is in status.go) | ||||
| 	pulsingTicker *time.Ticker | ||||
| ) | ||||
|   | ||||
| @@ -14,6 +14,7 @@ type UserPrefs struct { | ||||
| 	SaveSudoPassword        bool   `json:"save_sudo_password"` | ||||
| 	ShowTerminalNormally    bool   `json:"show_terminal_normally"` | ||||
| 	EnableVanillaTweaks     bool   `json:"enable_vanilla_tweaks"` | ||||
| 	RemapOptionAsAlt        bool   `json:"remap_option_as_alt"` | ||||
| } | ||||
|  | ||||
| func getPrefsPath() (string, error) { | ||||
|   | ||||
							
								
								
									
										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 | ||||
| } | ||||
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user
	 aomizu
					aomizu