返回 DeepSeek-Reasonix
graphics.ts
根目录 / desktop / electron / src / main / graphics.ts
1 import { existsSync, readFileSync, renameSync, writeFileSync, mkdirSync } from "node:fs";
2 import { dirname, join } from "node:path";
3
4 export type GraphicsOverride = "none" | "environment" | "command-line";
5 // Removed from process.argv after bootstrap so update/restart flows cannot inherit it.
6 export const GRAPHICS_RECOVERY_ARG = "--reasonix-graphics-recovery";
7 export function consumeGraphicsRecoveryArg(argv: string[]): boolean {
8 const temporary = argv.includes(GRAPHICS_RECOVERY_ARG);
9 for (let i = argv.length - 1; i >= 0; i--) if (argv[i] === GRAPHICS_RECOVERY_ARG) argv.splice(i, 1);
10 return temporary;
11 }
12 export type GraphicsWarning = "invalid-config" | "unreadable-config" | "unsupported-version";
13 export interface GraphicsSettingsState {
14 hardwareAcceleration: boolean;
15 startupEnabled: boolean;
16 override: GraphicsOverride;
17 restartRequired: boolean;
18 writable: boolean;
19 warning: GraphicsWarning | null;
20 }
21 export interface GraphicsBootstrap {
22 state: GraphicsSettingsState;
23 shouldDisable: boolean;
24 configPath: string;
25 }
26
27 type Stored = { version: 1; hardwareAcceleration: boolean; [key: string]: unknown };
28
29 function parseStored(path: string): { value: Stored | null; warning: GraphicsWarning | null } {
30 if (!existsSync(path)) return { value: null, warning: null };
31 try {
32 const parsed = JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
33 if (parsed.version !== 1) return { value: null, warning: "unsupported-version" };
34 if (typeof parsed.hardwareAcceleration !== "boolean") return { value: null, warning: "invalid-config" };
35 return { value: parsed as Stored, warning: null };
36 } catch {
37 return { value: null, warning: "unreadable-config" };
38 }
39 }
40
41 export function loadGraphicsBootstrap(dataHome: string, env: NodeJS.ProcessEnv, argv: readonly string[]): GraphicsBootstrap {
42 // `dataHome/desktop-shell` is Electron's userData profile (set by
43 // claimShellInstance), so keep the preference directly in that profile.
44 const configPath = join(dataHome, "graphics.json");
45 const parsed = parseStored(configPath);
46 const hardwareAcceleration = parsed.value?.hardwareAcceleration ?? true;
47 const environmentOverride = env.REASONIX_DISABLE_GPU === "1";
48 const commandLineOverride = argv.includes("--disable-gpu");
49 const override: GraphicsOverride = environmentOverride ? "environment" : commandLineOverride ? "command-line" : "none";
50 const startupEnabled = override === "none" && !argv.includes(GRAPHICS_RECOVERY_ARG) ? hardwareAcceleration : false;
51 return {
52 configPath,
53 shouldDisable: !startupEnabled,
54 state: {
55 hardwareAcceleration,
56 startupEnabled,
57 override,
58 restartRequired: override === "none" && hardwareAcceleration !== startupEnabled,
59 writable: parsed.warning !== "unsupported-version",
60 warning: parsed.warning,
61 },
62 };
63 }
64
65 export class GraphicsSettingsStore {
66 private stored: Stored | null;
67 constructor(private readonly path: string, bootstrap: GraphicsBootstrap) {
68 this.stored = parseStored(path).value ?? { version: 1, hardwareAcceleration: bootstrap.state.hardwareAcceleration };
69 this.state = bootstrap.state;
70 }
71 private state: GraphicsSettingsState;
72 private writeQueue: Promise<void> = Promise.resolve();
73 get current(): GraphicsSettingsState { return this.state; }
74 async setHardwareAcceleration(enabled: boolean): Promise<GraphicsSettingsState> {
75 if (typeof enabled !== "boolean") throw new Error("hardwareAcceleration must be boolean");
76 if (!this.state.writable) throw new Error("graphics settings are read-only");
77 const next: Stored = { ...this.stored, version: 1, hardwareAcceleration: enabled };
78 const write = this.writeQueue.catch(() => undefined).then(() => {
79 mkdirSync(dirname(this.path), { recursive: true });
80 const existing = parseStored(this.path);
81 if (existing.warning) {
82 let backup = `${this.path}.invalid`;
83 let suffix = 1;
84 while (existsSync(backup)) backup = `${this.path}.invalid.${suffix++}`;
85 renameSync(this.path, backup);
86 }
87 const temp = `${this.path}.tmp`;
88 writeFileSync(temp, `${JSON.stringify(next)}\n`, "utf8");
89 renameSync(temp, this.path);
90 });
91 this.writeQueue = write.catch(() => undefined);
92 await write;
93 this.stored = next;
94 this.state = { ...this.state, hardwareAcceleration: enabled, restartRequired: this.state.override === "none" && enabled !== this.state.startupEnabled, warning: null };
95 return this.state;
96 }
97 }
98
98 lines TYPESCRIPT