Skip to content

Commit 13ed7da

Browse files
committed
feat: linux(WIP)
1 parent 2cf69c1 commit 13ed7da

File tree

6 files changed

+176
-1
lines changed

6 files changed

+176
-1
lines changed

frontend/src/App.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ThemeSwitcher } from "./components/ThemeSwitcher";
1818
import { IoCloseOutline } from "react-icons/io5";
1919
import { FiMinimize2 } from "react-icons/fi";
2020
import { LeviIcon } from "./icons/LeviIcon";
21-
import { FaDownload, FaRocket, FaCog, FaList, FaEllipsisH, FaInfoCircle } from "react-icons/fa";
21+
import { FaDownload, FaRocket, FaCog, FaList, FaEllipsisH, FaInfoCircle, FaWineBottle } from "react-icons/fa";
2222
import { LauncherPage } from "./pages/LauncherPage";
2323
import { DownloadPage } from "./pages/DownloadPage";
2424
import { SplashScreen } from "./pages/SplashScreen";
@@ -43,6 +43,7 @@ import * as minecraft from "../bindings/github.com/liteldev/LeviLauncher/minecra
4343
import ReactMarkdown from "react-markdown";
4444
import remarkGfm from "remark-gfm";
4545
import AboutPage from "./pages/AboutPage";
46+
import WineGDKSetupPage from "./pages/WineGDKSetupPage";
4647
import OnboardingPage from "./pages/OnboardingPage";
4748

4849
function App() {
@@ -68,6 +69,7 @@ function App() {
6869
const [updateVersion, setUpdateVersion] = useState<string>("");
6970
const [updateBody, setUpdateBody] = useState<string>("");
7071
const [updateLoading, setUpdateLoading] = useState<boolean>(false);
72+
const [goos, setGoos] = useState<string>("");
7173

7274
const refresh = () => {
7375
setCount((prevCount) => {
@@ -231,6 +233,12 @@ function App() {
231233
} catch {}
232234
}, [hasBackend]);
233235

236+
useEffect(() => {
237+
try {
238+
minecraft?.GetRuntimeGOOS?.().then((s: string) => setGoos(String(s || ""))).catch(()=>{});
239+
} catch {}
240+
}, []);
241+
234242

235243

236244
const tryNavigate = (path: string) => {
@@ -405,6 +413,20 @@ function App() {
405413
{t("app.settings")}
406414
</Button>
407415
</Tooltip>
416+
{goos === "linux" && (
417+
<Tooltip content={"WineGDK 安装"} delay={0} closeDelay={0}>
418+
<Button
419+
variant="light"
420+
aria-label="WineGDK Setup"
421+
isDisabled={navLocked}
422+
onPress={() => { tryNavigate("/winegdk"); }}
423+
className={`px-3 rounded-2xl ${location.pathname === "/winegdk" ? "bg-default-200" : ""}`}
424+
startContent={<FaWineBottle size={18} />}
425+
>
426+
WineGDK
427+
</Button>
428+
</Tooltip>
429+
)}
408430
<Dropdown>
409431
<DropdownTrigger>
410432
<Button
@@ -519,6 +541,7 @@ function App() {
519541
/>
520542
<Route path="/content/skin-packs" element={<SkinPacksPage />} />
521543
<Route path="/about" element={<AboutPage />} />
544+
{goos === "linux" && <Route path="/winegdk" element={<WineGDKSetupPage />} />}
522545
</Routes>
523546
))}
524547
</motion.div>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React, { useEffect, useState } from "react";
2+
import { Button, Card, CardBody } from "@heroui/react";
3+
import { Events } from "@wailsio/runtime";
4+
import * as minecraft from "../../bindings/github.com/liteldev/LeviLauncher/minecraft";
5+
6+
export default function WineGDKSetupPage() {
7+
const [status, setStatus] = useState<string>("");
8+
const [error, setError] = useState<string>("");
9+
const [running, setRunning] = useState<boolean>(false);
10+
11+
useEffect(() => {
12+
const off1 = Events.On("winegdk.setup.status", (e: any) => {
13+
try { setStatus(String(e)); } catch {}
14+
});
15+
const off2 = Events.On("winegdk.setup.error", (e: any) => {
16+
try { setError(String(e)); setRunning(false); } catch {}
17+
});
18+
const off3 = Events.On("winegdk.setup.done", () => {
19+
setRunning(false);
20+
setStatus("done");
21+
});
22+
return () => {
23+
try { off1 && off1(); } catch {}
24+
try { off2 && off2(); } catch {}
25+
try { off3 && off3(); } catch {}
26+
};
27+
}, []);
28+
29+
const start = async () => {
30+
setError("");
31+
setStatus("start");
32+
setRunning(true);
33+
try {
34+
const rc = await minecraft.SetupWineGDK?.();
35+
if (rc && String(rc).trim() !== "") {
36+
setError(String(rc));
37+
setRunning(false);
38+
}
39+
} catch (e: any) {
40+
setError(String(e?.message || e || "ERR_START"));
41+
setRunning(false);
42+
}
43+
};
44+
45+
return (
46+
<div className="w-full h-full p-4">
47+
<div className="max-w-3xl mx-auto flex flex-col gap-4">
48+
<Card>
49+
<CardBody>
50+
<div className="text-lg font-semibold mb-2">WineGDK 安装</div>
51+
<div className="text-small text-default-700 mb-4">
52+
在 BaseRoot 目录下克隆并编译 WineGDK,随后安装前缀依赖(vkd3d、dxvk、dxvk_nvapi)。
53+
</div>
54+
<div className="flex items-center gap-3">
55+
<Button color="primary" isLoading={running} onPress={start}>
56+
开始安装
57+
</Button>
58+
{status && (
59+
<span className="text-small">当前状态:{status}</span>
60+
)}
61+
{error && (
62+
<span className="text-small text-danger">错误:{error}</span>
63+
)}
64+
</div>
65+
</CardBody>
66+
</Card>
67+
</div>
68+
</div>
69+
);
70+
}

internal/winegdk/setup_linux.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//go:build linux
2+
3+
package winegdk
4+
5+
import (
6+
"context"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"strings"
11+
"github.com/wailsapp/wails/v3/pkg/application"
12+
"github.com/liteldev/LeviLauncher/internal/utils"
13+
"os/ioutil"
14+
)
15+
16+
const (
17+
EventSetupStatus = "winegdk.setup.status"
18+
EventSetupProgress = "winegdk.setup.progress"
19+
EventSetupError = "winegdk.setup.error"
20+
EventSetupDone = "winegdk.setup.done"
21+
)
22+
23+
func Setup(ctx context.Context) string {
24+
base := utils.BaseRoot()
25+
if strings.TrimSpace(base) == "" { application.Get().Event.Emit(EventSetupError, "ERR_BASE_ROOT"); return "ERR_BASE_ROOT" }
26+
application.Get().Event.Emit(EventSetupStatus, "start")
27+
id := func() string {
28+
b, err := ioutil.ReadFile("/etc/os-release")
29+
if err != nil { return "" }
30+
for _, l := range strings.Split(string(b), "\n") {
31+
s := strings.TrimSpace(l)
32+
if strings.HasPrefix(s, "ID=") {
33+
v := strings.TrimPrefix(s, "ID=")
34+
v = strings.Trim(v, "\"'")
35+
return strings.ToLower(strings.TrimSpace(v))
36+
}
37+
}
38+
return ""
39+
}()
40+
if id == "arch" {
41+
application.Get().Event.Emit(EventSetupStatus, "deps_warning_arch")
42+
pkgs := "mingw-w64-gcc base-devel git gcc multilib-devel winetricks wine vulkan-icd-loader lib32-vulkan-icd-loader libx11 lib32-libx11 freetype2 lib32-freetype2 mesa lib32-mesa glu lib32-glu alsa-lib lib32-alsa-lib libxrandr lib32-libxrandr libxi lib32-libxi libxext lib32-libxext libxrender lib32-libxrender libxcursor lib32-libxcursor libxinerama lib32-libxinerama libxcomposite lib32-libxcomposite libxfixes lib32-libxfixes libpng lib32-libpng libjpeg-turbo lib32-libjpeg-turbo libtiff lib32-libtiff openal lib32-openal mpg123 lib32-mpg123 sdl2 lib32-sdl2 libxml2 lib32-libxml2 libldap lib32-libldap vulkan-headers cups"
43+
application.Get().Event.Emit(EventSetupStatus, "deps_install_arch")
44+
cmd := exec.Command("bash", "-c", "sudo pacman -S --needed --noconfirm "+pkgs)
45+
_ = cmd.Run()
46+
} else {
47+
application.Get().Event.Emit(EventSetupStatus, "deps_warning_other")
48+
}
49+
wg := filepath.Join(base, "WineGDK")
50+
if _, err := os.Stat(wg); err != nil {
51+
application.Get().Event.Emit(EventSetupStatus, "cloning")
52+
cmd := exec.Command("git", "clone", "https://github.com/Weather-OS/WineGDK.git", wg)
53+
if err := cmd.Run(); err != nil { application.Get().Event.Emit(EventSetupError, "ERR_GIT_CLONE"); return "ERR_GIT_CLONE" }
54+
}
55+
bd := filepath.Join(base, "build")
56+
_ = os.MkdirAll(bd, 0755)
57+
application.Get().Event.Emit(EventSetupStatus, "configuring")
58+
cfg := exec.Command("bash", "-c", "cd '"+bd+"' && '../WineGDK/configure' --enable-win64")
59+
if err := cfg.Run(); err != nil { application.Get().Event.Emit(EventSetupError, "ERR_CONFIGURE"); return "ERR_CONFIGURE" }
60+
application.Get().Event.Emit(EventSetupStatus, "compiling")
61+
mk := exec.Command("bash", "-c", "cd '"+bd+"' && make -j$(nproc)")
62+
if err := mk.Run(); err != nil { application.Get().Event.Emit(EventSetupError, "ERR_MAKE"); return "ERR_MAKE" }
63+
pf := filepath.Join(base, "prefix")
64+
_ = os.MkdirAll(pf, 0755)
65+
application.Get().Event.Emit(EventSetupStatus, "winetricks")
66+
wt := exec.Command("bash", "-c", "WINEPREFIX='"+pf+"' winetricks vkd3d dxvk dxvk_nvapi0061")
67+
if err := wt.Run(); err != nil { application.Get().Event.Emit(EventSetupError, "ERR_WINETRICKS"); return "ERR_WINETRICKS" }
68+
application.Get().Event.Emit(EventSetupDone, struct{}{})
69+
return ""
70+
}

internal/winegdk/setup_windows.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build windows
2+
3+
package winegdk
4+
5+
import "context"
6+
7+
func Setup(ctx context.Context) string { return "ERR_NOT_SUPPORTED" }

minecraft.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/liteldev/LeviLauncher/internal/utils"
2828
"github.com/liteldev/LeviLauncher/internal/vcruntime"
2929
"github.com/liteldev/LeviLauncher/internal/versions"
30+
"github.com/liteldev/LeviLauncher/internal/winegdk"
3031
)
3132

3233
const (
@@ -567,6 +568,10 @@ func (a *Minecraft) Update() bool {
567568
return true
568569
}
569570

571+
func (a *Minecraft) SetupWineGDK() string { return winegdk.Setup(a.ctx) }
572+
573+
func (a *Minecraft) GetRuntimeGOOS() string { return runtime.GOOS }
574+
570575
func (a *Minecraft) TestMirrorLatencies(urls []string, timeoutMs int) []map[string]interface{} {
571576
return mcservice.TestMirrorLatencies(urls, timeoutMs)
572577
}

0 commit comments

Comments
 (0)