integrated rosettax87 as a service
This commit is contained in:
@@ -2,5 +2,5 @@
|
|||||||
Icon = "Icon.png"
|
Icon = "Icon.png"
|
||||||
Name = "TurtleSilicon"
|
Name = "TurtleSilicon"
|
||||||
ID = "com.tairasu.turtlesilicon"
|
ID = "com.tairasu.turtlesilicon"
|
||||||
Version = "1.0.7"
|
Version = "1.1.0"
|
||||||
Build = 12
|
Build = 13
|
||||||
|
12
README.md
12
README.md
@@ -50,10 +50,14 @@ All credit for the core translation layer `winerosetta` and `rosettax87` goes to
|
|||||||
* Click "Patch TurtleWoW".
|
* Click "Patch TurtleWoW".
|
||||||
* Click "Patch CrossOver".
|
* Click "Patch CrossOver".
|
||||||
* Status indicators will turn green once patching is successful for each.
|
* Status indicators will turn green once patching is successful for each.
|
||||||
6. **Launch Game**:
|
6. **Start RosettaX87 Service**:
|
||||||
* Once both paths are set and both components are patched, the "Launch Game" button will become active. Click it.
|
* Click "Start RosettaX87 Service" and enter your sudo password when prompted.
|
||||||
* Follow the on-screen prompts (you will need to enter your password in a new Terminal window for `rosettax87`).
|
* This will run the RosettaX87 service in the background and is required for launching the game.
|
||||||
7. **Enjoy**: Experience a significantly smoother Turtle WoW on your Apple Silicon Mac!
|
* The service will automatically stop when you close the launcher.
|
||||||
|
7. **Launch Game**:
|
||||||
|
* Once both paths are set, both components are patched, and the RosettaX87 service is running, the "Launch Game" button will become active. Click it.
|
||||||
|
* The game will launch directly without requiring additional password prompts.
|
||||||
|
8. **Enjoy**: Experience a significantly smoother Turtle WoW on your Apple Silicon Mac!
|
||||||
|
|
||||||
### Method 2: Running from Source Code
|
### Method 2: Running from Source Code
|
||||||
|
|
||||||
|
10
main.go
10
main.go
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"turtlesilicon/pkg/service"
|
||||||
"turtlesilicon/pkg/ui"
|
"turtlesilicon/pkg/ui"
|
||||||
"turtlesilicon/pkg/utils"
|
"turtlesilicon/pkg/utils"
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ import (
|
|||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
const appVersion = "1.0.7"
|
const appVersion = "1.1.0"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
myApp := app.NewWithID("com.tairasu.turtlesilicon")
|
myApp := app.NewWithID("com.tairasu.turtlesilicon")
|
||||||
@@ -53,5 +54,12 @@ func main() {
|
|||||||
content := ui.CreateUI(myWindow)
|
content := ui.CreateUI(myWindow)
|
||||||
myWindow.SetContent(content)
|
myWindow.SetContent(content)
|
||||||
|
|
||||||
|
// Set up cleanup when window closes
|
||||||
|
myWindow.SetCloseIntercept(func() {
|
||||||
|
log.Println("Application closing, cleaning up RosettaX87 service...")
|
||||||
|
service.CleanupService()
|
||||||
|
myApp.Quit()
|
||||||
|
})
|
||||||
|
|
||||||
myWindow.ShowAndRun()
|
myWindow.ShowAndRun()
|
||||||
}
|
}
|
||||||
|
@@ -56,59 +56,39 @@ func LaunchGame(myWindow fyne.Window) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appleScriptSafeRosettaDir := utils.EscapeStringForAppleScript(rosettaInTurtlePath)
|
// Since RosettaX87 service is already running, we can directly launch WoW
|
||||||
cmd1Script := fmt.Sprintf("tell application \"Terminal\" to do script \"cd \" & quoted form of \"%s\" & \" && sudo ./rosettax87\"", appleScriptSafeRosettaDir)
|
log.Println("RosettaX87 service is running. Proceeding to launch WoW.")
|
||||||
|
|
||||||
log.Println("Launching rosettax87 (requires sudo password in new terminal)...")
|
if paths.CrossoverPath == "" || paths.TurtlewowPath == "" {
|
||||||
if !utils.RunOsascript(cmd1Script, myWindow) {
|
dialog.ShowError(fmt.Errorf("CrossOver path or TurtleWoW path is not set. Cannot launch WoW."), myWindow)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.ShowConfirm("Action Required",
|
mtlHudValue := "0"
|
||||||
"The rosetta x87 terminal has been initiated.\n\n"+
|
if EnableMetalHud {
|
||||||
"1. Please enter your sudo password in that new terminal window.\n"+
|
mtlHudValue = "1"
|
||||||
"2. Wait for rosetta x87 to fully start.\n\n"+
|
}
|
||||||
"Click Yes once rosetta x87 is running and you have entered the password.\n"+
|
|
||||||
"Click No to abort launching WoW.",
|
|
||||||
func(confirmed bool) {
|
|
||||||
if confirmed {
|
|
||||||
log.Println("User confirmed rosetta x87 is running. Proceeding to launch WoW.")
|
|
||||||
if paths.CrossoverPath == "" || paths.TurtlewowPath == "" {
|
|
||||||
dialog.ShowError(fmt.Errorf("CrossOver path or TurtleWoW path is not set. Cannot launch WoW."), myWindow)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mtlHudValue := "0"
|
// Prepare environment variables
|
||||||
if EnableMetalHud {
|
envVars := fmt.Sprintf(`WINEDLLOVERRIDES="d3d9=n,b" MTL_HUD_ENABLED=%s`, mtlHudValue)
|
||||||
mtlHudValue = "1"
|
if CustomEnvVars != "" {
|
||||||
}
|
envVars = CustomEnvVars + " " + envVars
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare environment variables
|
shellCmd := fmt.Sprintf(`cd %s && %s %s %s %s`,
|
||||||
envVars := fmt.Sprintf(`WINEDLLOVERRIDES="d3d9=n,b" MTL_HUD_ENABLED=%s`, mtlHudValue)
|
utils.QuotePathForShell(paths.TurtlewowPath),
|
||||||
if CustomEnvVars != "" {
|
envVars,
|
||||||
envVars = CustomEnvVars + " " + envVars
|
utils.QuotePathForShell(rosettaExecutable),
|
||||||
}
|
utils.QuotePathForShell(wineloader2Path),
|
||||||
|
utils.QuotePathForShell(wowExePath))
|
||||||
|
|
||||||
shellCmd := fmt.Sprintf(`cd %s && %s %s %s %s`,
|
escapedShellCmd := utils.EscapeStringForAppleScript(shellCmd)
|
||||||
utils.QuotePathForShell(paths.TurtlewowPath),
|
cmd2Script := fmt.Sprintf("tell application \"Terminal\" to do script \"%s\"", escapedShellCmd)
|
||||||
envVars,
|
|
||||||
utils.QuotePathForShell(rosettaExecutable),
|
|
||||||
utils.QuotePathForShell(wineloader2Path),
|
|
||||||
utils.QuotePathForShell(wowExePath))
|
|
||||||
|
|
||||||
escapedShellCmd := utils.EscapeStringForAppleScript(shellCmd)
|
log.Println("Executing WoW launch command via AppleScript...")
|
||||||
cmd2Script := fmt.Sprintf("tell application \"Terminal\" to do script \"%s\"", escapedShellCmd)
|
if !utils.RunOsascript(cmd2Script, myWindow) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.Println("Executing updated WoW launch command via AppleScript...")
|
log.Println("Launch command executed. Check the new terminal window.")
|
||||||
if !utils.RunOsascript(cmd2Script, myWindow) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Launch commands executed. Check the new terminal windows.")
|
|
||||||
dialog.ShowInformation("Launched", "World of Warcraft is starting. Enjoy.", myWindow)
|
|
||||||
} else {
|
|
||||||
log.Println("User cancelled WoW launch after rosetta x87 initiation.")
|
|
||||||
dialog.ShowInformation("Cancelled", "WoW launch was cancelled.", myWindow)
|
|
||||||
}
|
|
||||||
}, myWindow)
|
|
||||||
}
|
}
|
||||||
|
@@ -17,10 +17,12 @@ import (
|
|||||||
const DefaultCrossOverPath = "/Applications/CrossOver.app"
|
const DefaultCrossOverPath = "/Applications/CrossOver.app"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
CrossoverPath string
|
CrossoverPath string
|
||||||
TurtlewowPath string
|
TurtlewowPath string
|
||||||
PatchesAppliedTurtleWoW = false
|
PatchesAppliedTurtleWoW = false
|
||||||
PatchesAppliedCrossOver = false
|
PatchesAppliedCrossOver = false
|
||||||
|
RosettaX87ServiceRunning = false
|
||||||
|
ServiceStarting = false
|
||||||
)
|
)
|
||||||
|
|
||||||
func SelectCrossOverPath(myWindow fyne.Window, crossoverPathLabel *widget.RichText, updateAllStatuses func()) {
|
func SelectCrossOverPath(myWindow fyne.Window, crossoverPathLabel *widget.RichText, updateAllStatuses func()) {
|
||||||
|
234
pkg/service/service.go
Normal file
234
pkg/service/service.go
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"turtlesilicon/pkg/paths"
|
||||||
|
"turtlesilicon/pkg/utils"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ServiceRunning = false
|
||||||
|
serviceCmd *exec.Cmd
|
||||||
|
servicePID int
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartRosettaX87Service starts the RosettaX87 service with sudo privileges
|
||||||
|
func StartRosettaX87Service(myWindow fyne.Window, updateAllStatuses func()) {
|
||||||
|
log.Println("Starting RosettaX87 service...")
|
||||||
|
|
||||||
|
if paths.TurtlewowPath == "" {
|
||||||
|
dialog.ShowError(fmt.Errorf("TurtleWoW path not set. Please set it first"), myWindow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rosettaX87Dir := filepath.Join(paths.TurtlewowPath, "rosettax87")
|
||||||
|
rosettaX87Exe := filepath.Join(rosettaX87Dir, "rosettax87")
|
||||||
|
|
||||||
|
if !utils.PathExists(rosettaX87Exe) {
|
||||||
|
dialog.ShowError(fmt.Errorf("rosettax87 executable not found at %s. Please apply TurtleWoW patches first", rosettaX87Exe), myWindow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ServiceRunning {
|
||||||
|
dialog.ShowInformation("Service Status", "RosettaX87 service is already running.", myWindow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show password dialog
|
||||||
|
passwordEntry := widget.NewPasswordEntry()
|
||||||
|
passwordEntry.SetPlaceHolder("Enter your sudo password")
|
||||||
|
passwordEntry.Resize(fyne.NewSize(300, passwordEntry.MinSize().Height))
|
||||||
|
|
||||||
|
// Create a container with proper sizing
|
||||||
|
passwordForm := widget.NewForm(widget.NewFormItem("Password:", passwordEntry))
|
||||||
|
passwordContainer := container.NewVBox(
|
||||||
|
widget.NewLabel("Enter your sudo password to start the RosettaX87 service:"),
|
||||||
|
passwordForm,
|
||||||
|
)
|
||||||
|
passwordContainer.Resize(fyne.NewSize(400, 100))
|
||||||
|
|
||||||
|
// Create the dialog variable so we can reference it in the callback
|
||||||
|
var passwordDialog dialog.Dialog
|
||||||
|
|
||||||
|
// Define the confirm logic as a function so it can be reused
|
||||||
|
confirmFunc := func() {
|
||||||
|
password := passwordEntry.Text
|
||||||
|
if password == "" {
|
||||||
|
dialog.ShowError(fmt.Errorf("password cannot be empty"), myWindow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the dialog
|
||||||
|
passwordDialog.Hide()
|
||||||
|
|
||||||
|
// Set starting state
|
||||||
|
paths.ServiceStarting = true
|
||||||
|
fyne.Do(func() {
|
||||||
|
updateAllStatuses()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start the service in a goroutine
|
||||||
|
go func() {
|
||||||
|
err := startServiceWithPassword(rosettaX87Dir, rosettaX87Exe, password)
|
||||||
|
paths.ServiceStarting = false
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to start RosettaX87 service: %v", err)
|
||||||
|
fyne.Do(func() {
|
||||||
|
dialog.ShowError(fmt.Errorf("failed to start RosettaX87 service: %v", err), myWindow)
|
||||||
|
})
|
||||||
|
ServiceRunning = false
|
||||||
|
} else {
|
||||||
|
log.Println("RosettaX87 service started successfully")
|
||||||
|
ServiceRunning = true
|
||||||
|
}
|
||||||
|
fyne.Do(func() {
|
||||||
|
updateAllStatuses()
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Enter key support to password entry
|
||||||
|
passwordEntry.OnSubmitted = func(text string) {
|
||||||
|
confirmFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordDialog = dialog.NewCustomConfirm("Sudo Password Required", "Start Service", "Cancel",
|
||||||
|
passwordContainer,
|
||||||
|
func(confirmed bool) {
|
||||||
|
if !confirmed {
|
||||||
|
log.Println("Service start cancelled by user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
confirmFunc()
|
||||||
|
}, myWindow)
|
||||||
|
|
||||||
|
passwordDialog.Show()
|
||||||
|
|
||||||
|
// Focus the password entry after showing the dialog
|
||||||
|
myWindow.Canvas().Focus(passwordEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// startServiceWithPassword starts the service using sudo with the provided password
|
||||||
|
func startServiceWithPassword(workingDir, executable, password string) error {
|
||||||
|
// Use sudo with the password
|
||||||
|
cmd := exec.Command("sudo", "-S", executable)
|
||||||
|
cmd.Dir = workingDir
|
||||||
|
|
||||||
|
// Create a pipe to send the password
|
||||||
|
stdin, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create stdin pipe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the command
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the password
|
||||||
|
_, err = stdin.Write([]byte(password + "\n"))
|
||||||
|
if err != nil {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
return fmt.Errorf("failed to send password: %v", err)
|
||||||
|
}
|
||||||
|
stdin.Close()
|
||||||
|
|
||||||
|
// Store the command and PID for later termination
|
||||||
|
serviceCmd = cmd
|
||||||
|
servicePID = cmd.Process.Pid
|
||||||
|
|
||||||
|
// Wait a moment to see if the process starts successfully
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
// Check if the process is still running
|
||||||
|
if cmd.ProcessState != nil && cmd.ProcessState.Exited() {
|
||||||
|
return fmt.Errorf("process exited prematurely with code: %d", cmd.ProcessState.ExitCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("RosettaX87 service started with PID: %d", servicePID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopRosettaX87Service stops the running RosettaX87 service
|
||||||
|
func StopRosettaX87Service(myWindow fyne.Window, updateAllStatuses func()) {
|
||||||
|
log.Println("Stopping RosettaX87 service...")
|
||||||
|
|
||||||
|
if !ServiceRunning {
|
||||||
|
dialog.ShowInformation("Service Status", "RosettaX87 service is not running.", myWindow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceCmd != nil && serviceCmd.Process != nil {
|
||||||
|
// Send SIGTERM to gracefully stop the process
|
||||||
|
err := serviceCmd.Process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to send SIGTERM to process: %v", err)
|
||||||
|
// Try SIGKILL as fallback
|
||||||
|
err = serviceCmd.Process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to kill process: %v", err)
|
||||||
|
dialog.ShowError(fmt.Errorf("failed to stop service: %v", err), myWindow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the process to exit
|
||||||
|
go func() {
|
||||||
|
serviceCmd.Wait()
|
||||||
|
ServiceRunning = false
|
||||||
|
serviceCmd = nil
|
||||||
|
servicePID = 0
|
||||||
|
log.Println("RosettaX87 service stopped")
|
||||||
|
fyne.Do(func() {
|
||||||
|
dialog.ShowInformation("Service Stopped", "RosettaX87 service has been stopped.", myWindow)
|
||||||
|
updateAllStatuses()
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
ServiceRunning = false
|
||||||
|
updateAllStatuses()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsServiceRunning checks if the RosettaX87 service is currently running
|
||||||
|
func IsServiceRunning() bool {
|
||||||
|
if !ServiceRunning {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double-check by verifying the process is still alive
|
||||||
|
if serviceCmd != nil && serviceCmd.Process != nil {
|
||||||
|
// Check if process is still running
|
||||||
|
err := serviceCmd.Process.Signal(syscall.Signal(0))
|
||||||
|
if err != nil {
|
||||||
|
// Process is not running
|
||||||
|
ServiceRunning = false
|
||||||
|
serviceCmd = nil
|
||||||
|
servicePID = 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServiceRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanupService ensures the service is stopped when the application exits
|
||||||
|
func CleanupService() {
|
||||||
|
if ServiceRunning && serviceCmd != nil && serviceCmd.Process != nil {
|
||||||
|
log.Println("Cleaning up RosettaX87 service on application exit...")
|
||||||
|
serviceCmd.Process.Kill()
|
||||||
|
ServiceRunning = false
|
||||||
|
}
|
||||||
|
}
|
105
pkg/ui/ui.go
105
pkg/ui/ui.go
@@ -6,10 +6,12 @@ import (
|
|||||||
"os" // Added import for os.ReadFile
|
"os" // Added import for os.ReadFile
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"turtlesilicon/pkg/launcher" // Corrected import path
|
"turtlesilicon/pkg/launcher" // Corrected import path
|
||||||
"turtlesilicon/pkg/patching" // Corrected import path
|
"turtlesilicon/pkg/patching" // Corrected import path
|
||||||
"turtlesilicon/pkg/paths" // Corrected import path
|
"turtlesilicon/pkg/paths" // Corrected import path
|
||||||
|
"turtlesilicon/pkg/service" // Added service import
|
||||||
"turtlesilicon/pkg/utils" // Corrected import path
|
"turtlesilicon/pkg/utils" // Corrected import path
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
@@ -24,13 +26,17 @@ var (
|
|||||||
turtlewowPathLabel *widget.RichText
|
turtlewowPathLabel *widget.RichText
|
||||||
turtlewowStatusLabel *widget.RichText
|
turtlewowStatusLabel *widget.RichText
|
||||||
crossoverStatusLabel *widget.RichText
|
crossoverStatusLabel *widget.RichText
|
||||||
|
serviceStatusLabel *widget.RichText
|
||||||
launchButton *widget.Button
|
launchButton *widget.Button
|
||||||
patchTurtleWoWButton *widget.Button
|
patchTurtleWoWButton *widget.Button
|
||||||
patchCrossOverButton *widget.Button
|
patchCrossOverButton *widget.Button
|
||||||
unpatchTurtleWoWButton *widget.Button
|
unpatchTurtleWoWButton *widget.Button
|
||||||
unpatchCrossOverButton *widget.Button
|
unpatchCrossOverButton *widget.Button
|
||||||
|
startServiceButton *widget.Button
|
||||||
|
stopServiceButton *widget.Button
|
||||||
metalHudCheckbox *widget.Check
|
metalHudCheckbox *widget.Check
|
||||||
envVarsEntry *widget.Entry
|
envVarsEntry *widget.Entry
|
||||||
|
pulsingActive = false
|
||||||
)
|
)
|
||||||
|
|
||||||
func UpdateAllStatuses() {
|
func UpdateAllStatuses() {
|
||||||
@@ -131,12 +137,89 @@ func UpdateAllStatuses() {
|
|||||||
|
|
||||||
// Update Launch Button State
|
// Update Launch Button State
|
||||||
if launchButton != nil {
|
if launchButton != nil {
|
||||||
if paths.PatchesAppliedTurtleWoW && paths.PatchesAppliedCrossOver && paths.TurtlewowPath != "" && paths.CrossoverPath != "" {
|
// Now requires service to be running as well
|
||||||
|
if paths.PatchesAppliedTurtleWoW && paths.PatchesAppliedCrossOver &&
|
||||||
|
paths.TurtlewowPath != "" && paths.CrossoverPath != "" && service.IsServiceRunning() {
|
||||||
launchButton.Enable()
|
launchButton.Enable()
|
||||||
} else {
|
} else {
|
||||||
launchButton.Disable()
|
launchButton.Disable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update Service Status
|
||||||
|
if paths.ServiceStarting {
|
||||||
|
// Show pulsing "Starting..." when service is starting
|
||||||
|
if serviceStatusLabel != nil {
|
||||||
|
if !pulsingActive {
|
||||||
|
pulsingActive = true
|
||||||
|
go startPulsingAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if startServiceButton != nil {
|
||||||
|
startServiceButton.Disable()
|
||||||
|
}
|
||||||
|
if stopServiceButton != nil {
|
||||||
|
stopServiceButton.Disable()
|
||||||
|
}
|
||||||
|
} else if service.IsServiceRunning() {
|
||||||
|
pulsingActive = false
|
||||||
|
paths.RosettaX87ServiceRunning = true
|
||||||
|
if serviceStatusLabel != nil {
|
||||||
|
serviceStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Running", Style: widget.RichTextStyle{ColorName: theme.ColorNameSuccess}}}
|
||||||
|
serviceStatusLabel.Refresh()
|
||||||
|
}
|
||||||
|
if startServiceButton != nil {
|
||||||
|
startServiceButton.Disable()
|
||||||
|
}
|
||||||
|
if stopServiceButton != nil {
|
||||||
|
stopServiceButton.Enable()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pulsingActive = false
|
||||||
|
paths.RosettaX87ServiceRunning = false
|
||||||
|
if serviceStatusLabel != nil {
|
||||||
|
serviceStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: "Stopped", Style: widget.RichTextStyle{ColorName: theme.ColorNameError}}}
|
||||||
|
serviceStatusLabel.Refresh()
|
||||||
|
}
|
||||||
|
if startServiceButton != nil {
|
||||||
|
if paths.TurtlewowPath != "" && paths.PatchesAppliedTurtleWoW {
|
||||||
|
startServiceButton.Enable()
|
||||||
|
} else {
|
||||||
|
startServiceButton.Disable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stopServiceButton != nil {
|
||||||
|
stopServiceButton.Disable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startPulsingAnimation creates a pulsing effect for the "Starting..." text
|
||||||
|
func startPulsingAnimation() {
|
||||||
|
dots := 0
|
||||||
|
for pulsingActive && paths.ServiceStarting {
|
||||||
|
var text string
|
||||||
|
switch dots % 4 {
|
||||||
|
case 0:
|
||||||
|
text = "Starting"
|
||||||
|
case 1:
|
||||||
|
text = "Starting."
|
||||||
|
case 2:
|
||||||
|
text = "Starting.."
|
||||||
|
case 3:
|
||||||
|
text = "Starting..."
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceStatusLabel != nil {
|
||||||
|
fyne.DoAndWait(func() {
|
||||||
|
serviceStatusLabel.Segments = []widget.RichTextSegment{&widget.TextSegment{Text: text, Style: widget.RichTextStyle{ColorName: theme.ColorNamePrimary}}}
|
||||||
|
serviceStatusLabel.Refresh()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
dots++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateUI(myWindow fyne.Window) fyne.CanvasObject {
|
func CreateUI(myWindow fyne.Window) fyne.CanvasObject {
|
||||||
@@ -153,6 +236,7 @@ func CreateUI(myWindow fyne.Window) fyne.CanvasObject {
|
|||||||
turtlewowPathLabel = widget.NewRichText()
|
turtlewowPathLabel = widget.NewRichText()
|
||||||
turtlewowStatusLabel = widget.NewRichText()
|
turtlewowStatusLabel = widget.NewRichText()
|
||||||
crossoverStatusLabel = widget.NewRichText()
|
crossoverStatusLabel = widget.NewRichText()
|
||||||
|
serviceStatusLabel = widget.NewRichText()
|
||||||
|
|
||||||
// Load the application logo
|
// Load the application logo
|
||||||
logoResource, err := fyne.LoadResourceFromPath("Icon.png")
|
logoResource, err := fyne.LoadResourceFromPath("Icon.png")
|
||||||
@@ -212,6 +296,12 @@ func CreateUI(myWindow fyne.Window) fyne.CanvasObject {
|
|||||||
unpatchCrossOverButton = widget.NewButton("Unpatch CrossOver", func() {
|
unpatchCrossOverButton = widget.NewButton("Unpatch CrossOver", func() {
|
||||||
patching.UnpatchCrossOver(myWindow, UpdateAllStatuses)
|
patching.UnpatchCrossOver(myWindow, UpdateAllStatuses)
|
||||||
})
|
})
|
||||||
|
startServiceButton = widget.NewButton("Start Service", func() {
|
||||||
|
service.StartRosettaX87Service(myWindow, UpdateAllStatuses)
|
||||||
|
})
|
||||||
|
stopServiceButton = widget.NewButton("Stop Service", func() {
|
||||||
|
service.StopRosettaX87Service(myWindow, UpdateAllStatuses)
|
||||||
|
})
|
||||||
launchButton = widget.NewButton("Launch Game", func() {
|
launchButton = widget.NewButton("Launch Game", func() {
|
||||||
launcher.LaunchGame(myWindow)
|
launcher.LaunchGame(myWindow)
|
||||||
})
|
})
|
||||||
@@ -235,11 +325,24 @@ func CreateUI(myWindow fyne.Window) fyne.CanvasObject {
|
|||||||
container.NewGridWithColumns(4,
|
container.NewGridWithColumns(4,
|
||||||
widget.NewLabel("CrossOver Patch:"), crossoverStatusLabel, patchCrossOverButton, unpatchCrossOverButton,
|
widget.NewLabel("CrossOver Patch:"), crossoverStatusLabel, patchCrossOverButton, unpatchCrossOverButton,
|
||||||
),
|
),
|
||||||
|
container.NewGridWithColumns(4,
|
||||||
|
widget.NewLabel("RosettaX87 Service:"), serviceStatusLabel, startServiceButton, stopServiceButton,
|
||||||
|
),
|
||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
)
|
)
|
||||||
|
|
||||||
UpdateAllStatuses() // Initial UI state update
|
UpdateAllStatuses() // Initial UI state update
|
||||||
|
|
||||||
|
// Set up periodic status updates to keep service status in sync
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(5 * time.Second) // Check every 5 seconds
|
||||||
|
fyne.DoAndWait(func() {
|
||||||
|
UpdateAllStatuses()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Create GitHub link
|
// Create GitHub link
|
||||||
githubURL := "https://github.com/tairasu/TurtleSilicon"
|
githubURL := "https://github.com/tairasu/TurtleSilicon"
|
||||||
parsedURL, err := url.Parse(githubURL)
|
parsedURL, err := url.Parse(githubURL)
|
||||||
|
Reference in New Issue
Block a user