Skip to content

Commit b670ef8

Browse files
committed
feat: linux(WIP)
1 parent 6464727 commit b670ef8

File tree

9 files changed

+165
-92
lines changed

9 files changed

+165
-92
lines changed

frontend/src/App.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ function App() {
7070
const [updateBody, setUpdateBody] = useState<string>("");
7171
const [updateLoading, setUpdateLoading] = useState<boolean>(false);
7272
const [goos, setGoos] = useState<string>("");
73+
const [wineReady, setWineReady] = useState<boolean>(true);
74+
const [showWinePrompt, setShowWinePrompt] = useState<boolean>(false);
7375

7476
const refresh = () => {
7577
setCount((prevCount) => {
@@ -239,6 +241,40 @@ function App() {
239241
} catch {}
240242
}, []);
241243

244+
useEffect(() => {
245+
if (goos === "linux") {
246+
try {
247+
minecraft?.IsWineReady?.().then((ok: boolean) => {
248+
setWineReady(!!ok);
249+
if (!ok) {
250+
setNavLocked(true);
251+
setShowWinePrompt(true);
252+
navigate("/winegdk", { replace: true });
253+
}
254+
}).catch(()=>{
255+
setWineReady(false);
256+
setNavLocked(true);
257+
setShowWinePrompt(true);
258+
navigate("/winegdk", { replace: true });
259+
});
260+
} catch {
261+
setWineReady(false);
262+
setNavLocked(true);
263+
setShowWinePrompt(true);
264+
navigate("/winegdk", { replace: true });
265+
}
266+
}
267+
}, [goos]);
268+
269+
useEffect(() => {
270+
const off = Events.On("winegdk.setup.done", () => {
271+
setWineReady(true);
272+
setShowWinePrompt(false);
273+
setNavLocked(false);
274+
});
275+
return () => { try { off && off(); } catch {} };
276+
}, []);
277+
242278

243279

244280
const tryNavigate = (path: string) => {
@@ -508,6 +544,7 @@ function App() {
508544
(isFirstLoad ? (
509545
<></>
510546
) : (
547+
<>
511548
<Routes>
512549
<Route
513550
path="/"
@@ -543,6 +580,21 @@ function App() {
543580
<Route path="/about" element={<AboutPage />} />
544581
{goos === "linux" && <Route path="/winegdk" element={<WineGDKSetupPage />} />}
545582
</Routes>
583+
584+
{goos === "linux" && showWinePrompt && (
585+
<div className="fixed inset-0 bg-black/30 backdrop-blur-sm z-50 flex items-center justify-center">
586+
<div className="bg-content1 rounded-xl p-6 max-w-md w-full shadow-lg">
587+
<div className="text-lg font-semibold mb-2">{t("winegdk.prompt.title", { defaultValue: "需要安装 WineGDK" })}</div>
588+
<div className="text-small text-default-700 mb-4">
589+
{t("winegdk.prompt.desc", { defaultValue: "检测到未安装或不可用的 Wine;请先完成 WineGDK 安装再使用启动器。" })}
590+
</div>
591+
<div className="flex gap-3">
592+
<Button color="primary" onPress={() => tryNavigate("/winegdk")}>{t("winegdk.prompt.action", { defaultValue: "前往安装" })}</Button>
593+
</div>
594+
</div>
595+
</div>
596+
)}
597+
</>
546598
))}
547599
</motion.div>
548600

frontend/src/assets/locales/en_US.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,11 @@
460460
"start": "Start Setup",
461461
"status": "Status: ",
462462
"error": "Error: "
463+
},
464+
"prompt": {
465+
"title": "WineGDK Required",
466+
"desc": "Wine not installed or unavailable. Please complete WineGDK setup before using the launcher.",
467+
"action": "Go to Setup"
463468
}
464469
},
465470

frontend/src/assets/locales/zh_CN.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,11 @@
458458
"start": "开始安装",
459459
"status": "当前状态:",
460460
"error": "错误:"
461+
},
462+
"prompt": {
463+
"title": "需要安装 WineGDK",
464+
"desc": "检测到未安装或不可用的 Wine;请先完成 WineGDK 安装再使用启动器。",
465+
"action": "前往安装"
461466
}
462467
},
463468

internal/extractor/extractor_linux.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313

1414
"github.com/liteldev/LeviLauncher/internal/utils"
15+
"github.com/liteldev/LeviLauncher/internal/winegdk"
1516
)
1617

1718
func Init() {}
@@ -122,20 +123,9 @@ func MiHoYo(msixvcPath string, outDir string) (int, string) {
122123
return 1, "ERR_WRAPPER_NOT_FOUND"
123124
}
124125
// Prefer Wine built from WineGDK
125-
wine := filepath.Join(utils.BaseRoot(), "wine", "files", "bin", "wine")
126-
if _, err := os.Stat(wine); err != nil {
127-
wow := filepath.Join(utils.BaseRoot(), "wine", "files", "bin-wow64", "wine")
128-
if _, er2 := os.Stat(wow); er2 == nil {
129-
wine = wow
130-
} else {
131-
alt := filepath.Join(utils.BaseRoot(), "wine", "files", "bin", "wine64")
132-
if _, er3 := os.Stat(alt); er3 == nil { wine = alt } else { return 1, "ERR_WINE_NOT_AVAILABLE" }
133-
}
134-
}
135-
pf := filepath.Join(utils.BaseRoot(), "prefix")
136-
_ = os.MkdirAll(pf, 0755)
126+
wine := strings.TrimSpace(winegdk.FindWineBin())
127+
if wine == "" { return 1, "ERR_WINE_NOT_AVAILABLE" }
137128
cmd := exec.Command(wine, wrapper, msixvcPath, outDir, dll)
138-
cmd.Env = append(os.Environ(), "WINEPREFIX="+pf)
139129
if err := cmd.Run(); err != nil {
140130
return 1, "ERR_APPX_INSTALL_FAILED"
141131
}

internal/winegdk/setup_linux.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,8 @@ func Setup(ctx context.Context) string {
9090
if _, er3 := os.Stat(alt); er3 == nil { wineBin = alt } else { application.Get().Event.Emit(EventSetupError, "ERR_WINE_NOT_AVAILABLE"); return "ERR_WINE_NOT_AVAILABLE" }
9191
}
9292
}
93-
pf := filepath.Join(base, "prefix")
94-
_ = os.MkdirAll(pf, 0755)
9593
application.Get().Event.Emit(EventSetupStatus, "winetricks")
96-
wt := exec.Command("bash", "-c", "WINE='"+wineBin+"' WINEPREFIX='"+pf+"' winetricks vkd3d dxvk dxvk_nvapi0061")
94+
wt := exec.Command("bash", "-c", "WINE='"+wineBin+"' winetricks vkd3d dxvk dxvk_nvapi0061")
9795
if err := streamCmd(wt, "winetricks"); err != nil {
9896
application.Get().Event.Emit(EventSetupError, "ERR_WINETRICKS")
9997
return "ERR_WINETRICKS"

internal/winegdk/setup_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ package winegdk
44

55
import "context"
66

7-
func Setup(ctx context.Context) string { return "ERR_NOT_SUPPORTED" }
7+
func Setup(ctx context.Context) string { return "ERR_NOT_SUPPORTED" }

internal/winegdk/winebin_linux.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//go:build linux
2+
3+
package winegdk
4+
5+
import (
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
"github.com/liteldev/LeviLauncher/internal/utils"
11+
)
12+
13+
func FindWineBin() string {
14+
base := strings.TrimSpace(utils.BaseRoot())
15+
if base != "" {
16+
p := filepath.Join(base, "wine", "files", "bin", "wine")
17+
if fi, err := os.Stat(p); err == nil && fi.Mode().IsRegular() { return p }
18+
wow := filepath.Join(base, "wine", "files", "bin-wow64", "wine")
19+
if fi, err := os.Stat(wow); err == nil && fi.Mode().IsRegular() { return wow }
20+
alt := filepath.Join(base, "wine", "files", "bin", "wine64")
21+
if fi, err := os.Stat(alt); err == nil && fi.Mode().IsRegular() { return alt }
22+
}
23+
if sys, err := exec.LookPath("wine"); err == nil { return sys }
24+
return ""
25+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//go:build windows
2+
3+
package winegdk
4+
5+
func FindWineBin() string { return "" }

minecraft.go

Lines changed: 68 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
package main
22

33
import (
4-
"context"
5-
"log"
6-
"net/http"
7-
"os"
8-
"os/exec"
9-
"path/filepath"
10-
"runtime"
11-
"strings"
12-
13-
"github.com/liteldev/LeviLauncher/internal/discord"
14-
"github.com/wailsapp/wails/v3/pkg/application"
15-
16-
"github.com/liteldev/LeviLauncher/internal/content"
17-
"github.com/liteldev/LeviLauncher/internal/gameinput"
18-
"github.com/liteldev/LeviLauncher/internal/lang"
19-
"github.com/liteldev/LeviLauncher/internal/launch"
20-
"github.com/liteldev/LeviLauncher/internal/mcservice"
21-
"github.com/liteldev/LeviLauncher/internal/mods"
22-
"github.com/liteldev/LeviLauncher/internal/peeditor"
23-
"github.com/liteldev/LeviLauncher/internal/preloader"
24-
"github.com/liteldev/LeviLauncher/internal/registry"
25-
"github.com/liteldev/LeviLauncher/internal/types"
26-
"github.com/liteldev/LeviLauncher/internal/update"
27-
"github.com/liteldev/LeviLauncher/internal/utils"
28-
"github.com/liteldev/LeviLauncher/internal/vcruntime"
29-
"github.com/liteldev/LeviLauncher/internal/versions"
30-
"github.com/liteldev/LeviLauncher/internal/winegdk"
4+
"context"
5+
"log"
6+
"net/http"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"runtime"
11+
"strings"
12+
13+
"github.com/liteldev/LeviLauncher/internal/discord"
14+
"github.com/wailsapp/wails/v3/pkg/application"
15+
16+
"github.com/liteldev/LeviLauncher/internal/content"
17+
"github.com/liteldev/LeviLauncher/internal/gameinput"
18+
"github.com/liteldev/LeviLauncher/internal/lang"
19+
"github.com/liteldev/LeviLauncher/internal/launch"
20+
"github.com/liteldev/LeviLauncher/internal/mcservice"
21+
"github.com/liteldev/LeviLauncher/internal/mods"
22+
"github.com/liteldev/LeviLauncher/internal/peeditor"
23+
"github.com/liteldev/LeviLauncher/internal/preloader"
24+
"github.com/liteldev/LeviLauncher/internal/registry"
25+
"github.com/liteldev/LeviLauncher/internal/types"
26+
"github.com/liteldev/LeviLauncher/internal/update"
27+
"github.com/liteldev/LeviLauncher/internal/utils"
28+
"github.com/liteldev/LeviLauncher/internal/vcruntime"
29+
"github.com/liteldev/LeviLauncher/internal/versions"
30+
"github.com/liteldev/LeviLauncher/internal/winegdk"
3131
)
3232

3333
const (
@@ -168,13 +168,13 @@ func (a *Minecraft) EnsureGameInputInteractive() { go gameinput.EnsureInteractiv
168168
func (a *Minecraft) IsGameInputInstalled() bool { return gameinput.IsInstalled() }
169169

170170
func (a *Minecraft) IsGamingServicesInstalled() bool {
171-
if runtime.GOOS == "linux" {
172-
return true
173-
}
174-
if _, err := registry.GetAppxInfo("Microsoft.GamingServices"); err == nil {
175-
return true
176-
}
177-
return false
171+
if runtime.GOOS == "linux" {
172+
return true
173+
}
174+
if _, err := registry.GetAppxInfo("Microsoft.GamingServices"); err == nil {
175+
return true
176+
}
177+
return false
178178
}
179179

180180
func (a *Minecraft) StartMsixvcDownload(url string) string {
@@ -572,8 +572,13 @@ func (a *Minecraft) SetupWineGDK() string { return winegdk.Setup(a.ctx) }
572572

573573
func (a *Minecraft) GetRuntimeGOOS() string { return runtime.GOOS }
574574

575+
func (a *Minecraft) IsWineReady() bool {
576+
if runtime.GOOS != "linux" { return true }
577+
return strings.TrimSpace(winegdk.FindWineBin()) != ""
578+
}
579+
575580
func (a *Minecraft) TestMirrorLatencies(urls []string, timeoutMs int) []map[string]interface{} {
576-
return mcservice.TestMirrorLatencies(urls, timeoutMs)
581+
return mcservice.TestMirrorLatencies(urls, timeoutMs)
577582
}
578583

579584
func (a *Minecraft) launchVersionInternal(name string, checkRunning bool) string {
@@ -603,44 +608,34 @@ func (a *Minecraft) launchVersionInternal(name string, checkRunning bool) string
603608
}
604609
gameVer = strings.TrimSpace(m.GameVersion)
605610
}
606-
if checkRunning {
607-
if isProcessRunningAtPath(toRun) {
608-
return "ERR_GAME_ALREADY_RUNNING"
609-
}
610-
}
611-
var cmd *exec.Cmd
612-
if runtime.GOOS == "linux" {
613-
base := utils.BaseRoot()
614-
wine := filepath.Join(base, "wine", "files", "bin", "wine")
615-
if _, err := os.Stat(wine); err != nil {
616-
wow := filepath.Join(base, "wine", "files", "bin-wow64", "wine")
617-
if _, er2 := os.Stat(wow); er2 == nil {
618-
wine = wow
619-
} else {
620-
alt := filepath.Join(base, "wine", "files", "bin", "wine64")
621-
if _, er3 := os.Stat(alt); er3 == nil { wine = alt } else { return "ERR_WINE_NOT_AVAILABLE" }
622-
}
623-
}
624-
pf := filepath.Join(base, "prefix")
625-
_ = os.MkdirAll(pf, 0755)
626-
var argv []string
627-
argv = append(argv, toRun)
628-
argv = append(argv, args...)
629-
cmd = exec.Command(wine, argv...)
630-
cmd.Env = append(os.Environ(), "WINEPREFIX="+pf)
631-
} else {
632-
cmd = exec.Command(toRun, args...)
633-
}
634-
cmd.Dir = filepath.Dir(toRun)
635-
cmd.Stdout = os.Stdout
636-
cmd.Stderr = os.Stderr
637-
cmd.Stdin = os.Stdin
638-
if err := cmd.Start(); err != nil {
639-
return "ERR_LAUNCH_GAME"
640-
}
641-
discord.SetPlayingVersion(gameVer)
642-
go launch.MonitorMinecraftWindow(a.ctx)
643-
return ""
611+
if checkRunning {
612+
if isProcessRunningAtPath(toRun) {
613+
return "ERR_GAME_ALREADY_RUNNING"
614+
}
615+
}
616+
var cmd *exec.Cmd
617+
if runtime.GOOS == "linux" {
618+
wine := winegdk.FindWineBin()
619+
if strings.TrimSpace(wine) == "" {
620+
return "ERR_WINE_NOT_AVAILABLE"
621+
}
622+
var argv []string
623+
argv = append(argv, toRun)
624+
argv = append(argv, args...)
625+
cmd = exec.Command(wine, argv...)
626+
} else {
627+
cmd = exec.Command(toRun, args...)
628+
}
629+
cmd.Dir = filepath.Dir(toRun)
630+
cmd.Stdout = os.Stdout
631+
cmd.Stderr = os.Stderr
632+
cmd.Stdin = os.Stdin
633+
if err := cmd.Start(); err != nil {
634+
return "ERR_LAUNCH_GAME"
635+
}
636+
discord.SetPlayingVersion(gameVer)
637+
go launch.MonitorMinecraftWindow(a.ctx)
638+
return ""
644639
}
645640

646641
func (a *Minecraft) GetBaseRoot() string { return mcservice.GetBaseRoot() }
@@ -650,5 +645,3 @@ func (a *Minecraft) SetBaseRoot(root string) string { return mcservice.SetBaseRo
650645
func (a *Minecraft) ResetBaseRoot() string { return mcservice.ResetBaseRoot() }
651646

652647
func (a *Minecraft) CanWriteToDir(path string) bool { return mcservice.CanWriteToDir(path) }
653-
654-
// isProcessRunningAtPath implemented per-OS

0 commit comments

Comments
 (0)