Initial commit
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
export const PHASE_EXTRACTED_APP = "extracted-app";
|
||||
export const PHASE_MAIN_BUNDLE = "main-bundle";
|
||||
|
||||
export const PATCH_PHASES = [PHASE_EXTRACTED_APP, PHASE_MAIN_BUNDLE];
|
||||
|
||||
const ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
||||
|
||||
export function definePatch(descriptor) {
|
||||
if (descriptor == null || typeof descriptor !== "object" || Array.isArray(descriptor)) {
|
||||
throw new TypeError("Patch descriptor must be an object");
|
||||
}
|
||||
if (typeof descriptor.id !== "string" || !ID_PATTERN.test(descriptor.id)) {
|
||||
throw new TypeError(`Patch descriptor id must match ${ID_PATTERN}`);
|
||||
}
|
||||
if (!PATCH_PHASES.includes(descriptor.phase)) {
|
||||
throw new TypeError(`Patch '${descriptor.id}' has unsupported phase '${descriptor.phase}'`);
|
||||
}
|
||||
if (!Number.isInteger(descriptor.order)) {
|
||||
throw new TypeError(`Patch '${descriptor.id}' order must be an integer`);
|
||||
}
|
||||
if (typeof descriptor.required !== "boolean") {
|
||||
throw new TypeError(`Patch '${descriptor.id}' required must be a boolean`);
|
||||
}
|
||||
if (typeof descriptor.apply !== "function") {
|
||||
throw new TypeError(`Patch '${descriptor.id}' must export an apply function`);
|
||||
}
|
||||
|
||||
return Object.freeze({ ...descriptor });
|
||||
}
|
||||
|
||||
export function normalizeDescriptors(descriptors) {
|
||||
const normalized = descriptors.map(definePatch);
|
||||
const seen = new Set();
|
||||
for (const descriptor of normalized) {
|
||||
if (seen.has(descriptor.id)) {
|
||||
throw new Error(`Duplicate patch descriptor id '${descriptor.id}'`);
|
||||
}
|
||||
seen.add(descriptor.id);
|
||||
}
|
||||
|
||||
return [...normalized].sort((left, right) => {
|
||||
const phaseOrder = PATCH_PHASES.indexOf(left.phase) - PATCH_PHASES.indexOf(right.phase);
|
||||
return phaseOrder || left.order - right.order || left.id.localeCompare(right.id);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { definePatch, PHASE_MAIN_BUNDLE } from "./descriptor.mjs";
|
||||
import { markerStatus, replaceExactly } from "./lib.mjs";
|
||||
|
||||
export const UPDATER_MARKER = "/* nani-linux:disable-updater */";
|
||||
export const UPDATER_ENABLE_ANCHOR = 'const ue=process.platform!=="win32"';
|
||||
export const UPDATER_HANDLERS_ANCHOR = ",ro=Ue,ao=Ca,so=In,oo=xa,io=";
|
||||
|
||||
export default definePatch({
|
||||
id: "disable-updater",
|
||||
phase: PHASE_MAIN_BUNDLE,
|
||||
order: 20,
|
||||
required: true,
|
||||
apply({ source }) {
|
||||
const status = markerStatus(source, UPDATER_MARKER);
|
||||
if (status) return { source, status };
|
||||
let patched = replaceExactly(
|
||||
source,
|
||||
UPDATER_ENABLE_ANCHOR,
|
||||
`const ue=process.platform==="darwin"${UPDATER_MARKER}`,
|
||||
"updater platform predicate",
|
||||
);
|
||||
patched = replaceExactly(
|
||||
patched,
|
||||
UPDATER_HANDLERS_ANCHOR,
|
||||
',ro=process.platform==="linux"?async()=>void 0:Ue,ao=Ca,so=process.platform==="linux"?()=>void 0:In,oo=process.platform==="linux"?async()=>!1:xa,io=',
|
||||
"updater IPC handlers",
|
||||
);
|
||||
return { source: patched, status: "applied" };
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { cpSync, existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { normalizeDescriptors, PHASE_EXTRACTED_APP, PHASE_MAIN_BUNDLE } from "./descriptor.mjs";
|
||||
|
||||
function reportEntry(descriptor, status, detail = null) {
|
||||
return {
|
||||
id: descriptor.id,
|
||||
phase: descriptor.phase,
|
||||
order: descriptor.order,
|
||||
required: descriptor.required,
|
||||
status,
|
||||
...(detail ? { detail } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function validateResult(descriptor, result) {
|
||||
if (result == null || typeof result !== "object") {
|
||||
throw new Error(`Patch '${descriptor.id}' returned no result`);
|
||||
}
|
||||
if (result.status !== "applied" && result.status !== "already-applied") {
|
||||
throw new Error(`Patch '${descriptor.id}' returned invalid status '${result.status}'`);
|
||||
}
|
||||
if (descriptor.phase === PHASE_MAIN_BUNDLE && typeof result.source !== "string") {
|
||||
throw new Error(`Main-bundle patch '${descriptor.id}' must return source`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function runPatchDescriptors({ extractedDir, mainBundlePath, descriptors }) {
|
||||
const ordered = normalizeDescriptors(descriptors);
|
||||
const entries = [];
|
||||
let mainSource = readFileSync(mainBundlePath, "utf8");
|
||||
let hasRequiredFailure = false;
|
||||
let hasIntegrityFailure = false;
|
||||
|
||||
for (const descriptor of ordered) {
|
||||
const snapshot = descriptor.phase === PHASE_EXTRACTED_APP
|
||||
? `${extractedDir}.rollback-${descriptor.id}`
|
||||
: mainSource;
|
||||
|
||||
try {
|
||||
if (descriptor.phase === PHASE_EXTRACTED_APP) {
|
||||
rmSync(snapshot, { recursive: true, force: true });
|
||||
cpSync(extractedDir, snapshot, { recursive: true, dereference: false });
|
||||
}
|
||||
|
||||
const result = validateResult(
|
||||
descriptor,
|
||||
descriptor.apply(
|
||||
descriptor.phase === PHASE_MAIN_BUNDLE
|
||||
? { extractedDir, mainBundlePath, source: mainSource }
|
||||
: { extractedDir, mainBundlePath },
|
||||
),
|
||||
);
|
||||
if (descriptor.phase === PHASE_MAIN_BUNDLE) mainSource = result.source;
|
||||
entries.push(reportEntry(descriptor, result.status, result.detail));
|
||||
} catch (error) {
|
||||
try {
|
||||
if (descriptor.phase === PHASE_EXTRACTED_APP && existsSync(snapshot)) {
|
||||
rmSync(extractedDir, { recursive: true, force: true });
|
||||
cpSync(snapshot, extractedDir, { recursive: true, dereference: false });
|
||||
}
|
||||
} catch (rollbackError) {
|
||||
hasIntegrityFailure = true;
|
||||
entries.push(reportEntry(
|
||||
descriptor,
|
||||
"failed-integrity",
|
||||
`${error.message}; rollback failed: ${rollbackError.message}`,
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
const status = descriptor.required ? "failed-required" : "skipped-optional";
|
||||
if (descriptor.required) hasRequiredFailure = true;
|
||||
entries.push(reportEntry(descriptor, status, error instanceof Error ? error.message : String(error)));
|
||||
} finally {
|
||||
if (descriptor.phase === PHASE_EXTRACTED_APP) {
|
||||
rmSync(snapshot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRequiredFailure && !hasIntegrityFailure) {
|
||||
writeFileSync(mainBundlePath, mainSource);
|
||||
}
|
||||
|
||||
return { entries, hasRequiredFailure, hasIntegrityFailure, mainSource };
|
||||
}
|
||||
|
||||
export function addIntegrityFailure(entries, id, detail) {
|
||||
entries.push({
|
||||
id,
|
||||
phase: "post-repack",
|
||||
order: 0,
|
||||
required: true,
|
||||
status: "failed-integrity",
|
||||
detail,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import disableUpdater from "./disable-updater.mjs";
|
||||
import linuxOnboarding from "./linux-onboarding.mjs";
|
||||
import linuxTray from "./linux-tray.mjs";
|
||||
import ocrStub from "./ocr-stub.mjs";
|
||||
import pruneNonLinux from "./prune-non-linux.mjs";
|
||||
import xdgAutostart from "./xdg-autostart.mjs";
|
||||
|
||||
export const corePatchDescriptors = [
|
||||
pruneNonLinux,
|
||||
ocrStub,
|
||||
linuxOnboarding,
|
||||
linuxTray,
|
||||
disableUpdater,
|
||||
xdgAutostart,
|
||||
];
|
||||
@@ -0,0 +1,66 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
export function countOccurrences(source, needle) {
|
||||
if (needle.length === 0) throw new Error("Patch needle must not be empty");
|
||||
let count = 0;
|
||||
let offset = 0;
|
||||
while ((offset = source.indexOf(needle, offset)) !== -1) {
|
||||
count += 1;
|
||||
offset += needle.length;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
export function replaceExactly(source, needle, replacement, label) {
|
||||
const matches = countOccurrences(source, needle);
|
||||
if (matches !== 1) {
|
||||
throw new Error(`${label}: expected exactly one semantic anchor, found ${matches}`);
|
||||
}
|
||||
return source.replace(needle, replacement);
|
||||
}
|
||||
|
||||
export function markerStatus(source, marker) {
|
||||
const matches = countOccurrences(source, marker);
|
||||
if (matches > 1) throw new Error(`Patch marker '${marker}' occurs ${matches} times`);
|
||||
return matches === 1 ? "already-applied" : null;
|
||||
}
|
||||
|
||||
export function copyTreeContents(sourceDir, destinationDir) {
|
||||
if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) {
|
||||
throw new Error(`Replacement tree is not a directory: ${sourceDir}`);
|
||||
}
|
||||
mkdirSync(destinationDir, { recursive: true });
|
||||
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
||||
cpSync(path.join(sourceDir, entry.name), path.join(destinationDir, entry.name), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
dereference: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function sha256File(filePath) {
|
||||
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
|
||||
}
|
||||
|
||||
export function walkFiles(root) {
|
||||
if (!existsSync(root)) return [];
|
||||
const files = [];
|
||||
const visit = (directory) => {
|
||||
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
||||
const fullPath = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) visit(fullPath);
|
||||
else if (entry.isFile()) files.push(fullPath);
|
||||
}
|
||||
};
|
||||
visit(root);
|
||||
return files.sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function relativeFileHashes(root) {
|
||||
return Object.fromEntries(
|
||||
walkFiles(root).map((filePath) => [path.relative(root, filePath), sha256File(filePath)]),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { definePatch, PHASE_EXTRACTED_APP } from "./descriptor.mjs";
|
||||
import { replaceExactly, walkFiles } from "./lib.mjs";
|
||||
|
||||
export const ONBOARDING_MARKER = "/* nani-linux:onboarding */";
|
||||
export const ONBOARDING_ANCHOR =
|
||||
'window.constants.os==="win32"?e("welcomeToWinApp"):e("welcomeToMacApp")';
|
||||
const ONBOARDING_REPLACEMENT =
|
||||
`window.constants.os==="linux"?e("welcomeToLinuxApp")${ONBOARDING_MARKER}:` +
|
||||
ONBOARDING_ANCHOR;
|
||||
|
||||
const TRANSLATIONS = {
|
||||
en: "Welcome to Nani for Linux!",
|
||||
ja: "NaniのLinuxアプリへようこそ!",
|
||||
ko: "Nani Linux 앱에 오신 걸 환영해요!",
|
||||
zh: "欢迎来到 Nani 的 Linux 应用!",
|
||||
};
|
||||
|
||||
function dictionaryPath(root, language) {
|
||||
return path.join(
|
||||
root,
|
||||
"node_modules/repo-lib/dist/dictionaries/default",
|
||||
`${language}.mjs`,
|
||||
);
|
||||
}
|
||||
|
||||
export function verifyLinuxOnboarding(root) {
|
||||
const rendererMatches = walkFiles(path.join(root, "out/renderer/assets"))
|
||||
.filter((file) => file.endsWith(".js"))
|
||||
.filter((file) => readFileSync(file, "utf8").includes(ONBOARDING_MARKER));
|
||||
if (rendererMatches.length !== 1) {
|
||||
throw new Error(`Expected one Linux onboarding renderer marker, found ${rendererMatches.length}`);
|
||||
}
|
||||
for (const [language, translation] of Object.entries(TRANSLATIONS)) {
|
||||
const source = readFileSync(dictionaryPath(root, language), "utf8");
|
||||
if (!source.includes(`welcomeToLinuxApp:\`${translation}\``)) {
|
||||
throw new Error(`Linux onboarding translation is missing: ${language}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default definePatch({
|
||||
id: "linux-onboarding",
|
||||
phase: PHASE_EXTRACTED_APP,
|
||||
order: 20,
|
||||
required: true,
|
||||
apply({ extractedDir }) {
|
||||
const rendererFiles = walkFiles(path.join(extractedDir, "out/renderer/assets"))
|
||||
.filter((file) => file.endsWith(".js"));
|
||||
const marked = rendererFiles.filter((file) =>
|
||||
readFileSync(file, "utf8").includes(ONBOARDING_MARKER));
|
||||
if (marked.length === 1) {
|
||||
verifyLinuxOnboarding(extractedDir);
|
||||
return { status: "already-applied" };
|
||||
}
|
||||
if (marked.length > 0) throw new Error("Linux onboarding patch is partially applied");
|
||||
|
||||
const matches = rendererFiles.filter((file) =>
|
||||
readFileSync(file, "utf8").includes(ONBOARDING_ANCHOR));
|
||||
if (matches.length !== 1) {
|
||||
throw new Error(`Expected one Linux onboarding anchor, found ${matches.length}`);
|
||||
}
|
||||
const rendererSource = readFileSync(matches[0], "utf8");
|
||||
writeFileSync(
|
||||
matches[0],
|
||||
replaceExactly(rendererSource, ONBOARDING_ANCHOR, ONBOARDING_REPLACEMENT, "Linux onboarding"),
|
||||
);
|
||||
|
||||
for (const [language, translation] of Object.entries(TRANSLATIONS)) {
|
||||
const file = dictionaryPath(extractedDir, language);
|
||||
const source = readFileSync(file, "utf8");
|
||||
const anchor = "welcomeToWinApp:";
|
||||
writeFileSync(
|
||||
file,
|
||||
replaceExactly(
|
||||
source,
|
||||
anchor,
|
||||
`welcomeToLinuxApp:\`${translation}\`,${anchor}`,
|
||||
`Linux onboarding dictionary (${language})`,
|
||||
),
|
||||
);
|
||||
}
|
||||
verifyLinuxOnboarding(extractedDir);
|
||||
return { status: "applied" };
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { definePatch, PHASE_MAIN_BUNDLE } from "./descriptor.mjs";
|
||||
import { markerStatus, replaceExactly } from "./lib.mjs";
|
||||
|
||||
export const TRAY_MARKER = "/* nani-linux:linux-tray */";
|
||||
export const TRAY_ANCHOR = 'function Ls(e){if(!it()&&process.platform==="win32"){const t=new c.Tray(S.join(__dirname,"../../resources/tray-icon-win.ico"));Kn(t),Ce(t,e);return}Ce(null,e)}';
|
||||
const TRAY_REPLACEMENT = `function Ls(e){${TRAY_MARKER}if(!it()&&(process.platform==="win32"||process.platform==="linux")){const t=new c.Tray(process.platform==="linux"?c.nativeImage.createFromPath(S.join(__dirname,"../../resources/icon.png")).resize({width:24,height:24}):S.join(__dirname,"../../resources/tray-icon-win.ico"));Kn(t),Ce(t,e);return}Ce(null,e)}`;
|
||||
|
||||
export default definePatch({
|
||||
id: "linux-tray",
|
||||
phase: PHASE_MAIN_BUNDLE,
|
||||
order: 10,
|
||||
required: true,
|
||||
apply({ source }) {
|
||||
const status = markerStatus(source, TRAY_MARKER);
|
||||
if (status) return { source, status };
|
||||
return {
|
||||
source: replaceExactly(source, TRAY_ANCHOR, TRAY_REPLACEMENT, "Linux tray"),
|
||||
status: "applied",
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { definePatch, PHASE_EXTRACTED_APP } from "./descriptor.mjs";
|
||||
import { copyTreeContents, relativeFileHashes } from "./lib.mjs";
|
||||
|
||||
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
export const OCR_STUB_SOURCE = path.resolve(
|
||||
moduleDir,
|
||||
"../../stubs/@napi-rs/system-ocr-linux-x64-gnu",
|
||||
);
|
||||
export const OCR_STUB_RELATIVE = "node_modules/@napi-rs/system-ocr-linux-x64-gnu";
|
||||
|
||||
export function verifyOcrStub(extractedDir) {
|
||||
const destination = path.join(extractedDir, OCR_STUB_RELATIVE);
|
||||
if (!existsSync(destination)) throw new Error("Linux OCR stub is missing after repack");
|
||||
const expected = relativeFileHashes(OCR_STUB_SOURCE);
|
||||
const actual = relativeFileHashes(destination);
|
||||
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
||||
throw new Error("Linux OCR stub differs from the repository stub");
|
||||
}
|
||||
const packageJson = JSON.parse(readFileSync(path.join(destination, "package.json"), "utf8"));
|
||||
if (packageJson.name !== "@napi-rs/system-ocr-linux-x64-gnu") {
|
||||
throw new Error("Linux OCR stub package name is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
export default definePatch({
|
||||
id: "ocr-stub",
|
||||
phase: PHASE_EXTRACTED_APP,
|
||||
order: 10,
|
||||
required: true,
|
||||
apply({ extractedDir }) {
|
||||
const destination = path.join(extractedDir, OCR_STUB_RELATIVE);
|
||||
if (existsSync(destination)) {
|
||||
verifyOcrStub(extractedDir);
|
||||
return { status: "already-applied", detail: OCR_STUB_RELATIVE };
|
||||
}
|
||||
copyTreeContents(OCR_STUB_SOURCE, destination);
|
||||
verifyOcrStub(extractedDir);
|
||||
return { status: "applied", detail: OCR_STUB_RELATIVE };
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { existsSync, rmSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { definePatch, PHASE_EXTRACTED_APP } from "./descriptor.mjs";
|
||||
|
||||
export const NON_LINUX_PAYLOADS = [
|
||||
"native/build/Release/maccopy.node",
|
||||
"native/build/Release/macos26_window_corner.node",
|
||||
"node_modules/@napi-rs/system-ocr-darwin-arm64",
|
||||
"node_modules/@nut-tree-fork/libnut-darwin",
|
||||
"node_modules/@nut-tree-fork/libnut-win32",
|
||||
"node_modules/@nut-tree-fork/node-mac-permissions",
|
||||
"bin/language-helper",
|
||||
];
|
||||
|
||||
export function verifyNonLinuxPayloadsAbsent(root) {
|
||||
const remaining = NON_LINUX_PAYLOADS.filter((relativePath) =>
|
||||
existsSync(path.join(root, relativePath)),
|
||||
);
|
||||
if (remaining.length > 0) {
|
||||
throw new Error(`Non-Linux payloads remain: ${remaining.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function verifyNonLinuxHeaderEntries(entries) {
|
||||
const normalized = entries.map((entry) => entry.replace(/^\//, ""));
|
||||
const remaining = NON_LINUX_PAYLOADS.filter((forbidden) =>
|
||||
normalized.some((entry) => entry === forbidden || entry.startsWith(`${forbidden}/`)),
|
||||
);
|
||||
if (remaining.length > 0) {
|
||||
throw new Error(`Non-Linux payloads remain in ASAR header: ${remaining.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default definePatch({
|
||||
id: "prune-non-linux",
|
||||
phase: PHASE_EXTRACTED_APP,
|
||||
order: 5,
|
||||
required: true,
|
||||
apply({ extractedDir }) {
|
||||
const removed = [];
|
||||
for (const relativePath of NON_LINUX_PAYLOADS) {
|
||||
const target = path.join(extractedDir, relativePath);
|
||||
if (!existsSync(target)) continue;
|
||||
rmSync(target, { recursive: true, force: true });
|
||||
removed.push(relativePath);
|
||||
}
|
||||
verifyNonLinuxPayloadsAbsent(extractedDir);
|
||||
return {
|
||||
status: removed.length > 0 ? "applied" : "already-applied",
|
||||
detail: removed.length > 0 ? `removed ${removed.length} non-Linux payloads` : undefined,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { definePatch, PHASE_MAIN_BUNDLE } from "./descriptor.mjs";
|
||||
import { markerStatus, replaceExactly } from "./lib.mjs";
|
||||
|
||||
export const AUTOSTART_MARKER = "/* nani-linux:xdg-autostart */";
|
||||
export const AUTOSTART_GET_ANCHOR = ',bi=(async()=>({autoLaunchEnabled:c.app.getLoginItemSettings().openAtLogin})),Ai="ms-settings:startupapps";';
|
||||
export const AUTOSTART_SET_ANCHOR = 'const Mi=(async(e,t)=>{if(process.platform==="win32")return await vi();try{return c.app.setLoginItemSettings({openAtLogin:t,openAsHidden:!0}),{ok:!0}}catch(n){return{ok:!1,message:n instanceof Error?n.message:"Unknown error"}}})';
|
||||
|
||||
const AUTOSTART_GET_REPLACEMENT = String.raw`,$naniAutostartPath=()=>S.join(process.env.XDG_CONFIG_HOME||S.join(c.app.getPath("home"),".config"),"autostart","nani.desktop"),$naniDesktopQuote=e=>'"'+e.replace(/([\\"\u0060$])/g,"\\$1")+'"',$naniGetAutostart=async()=>te.access($naniAutostartPath()).then(()=>!0,()=>!1),$naniSetAutostart=async e=>{const t=$naniAutostartPath();if(!e)return await te.rm(t,{force:!0}),{ok:!0};const n=process.env.NANI_LAUNCHER_PATH;if(!n||!S.isAbsolute(n)||/[\r\n]/.test(n))return{ok:!1,message:"NANI_LAUNCHER_PATH must be an absolute path"};await te.mkdir(S.dirname(t),{recursive:!0});const r=t+".tmp-"+process.pid+"-"+Date.now(),a="[Desktop Entry]\nType=Application\nName=Nani Translate\nExec="+$naniDesktopQuote(n)+" --autostart\nTerminal=false\nX-GNOME-Autostart-enabled=true\n";try{return await te.writeFile(r,a,{mode:420}),await te.rename(r,t),{ok:!0}}finally{await te.rm(r,{force:!0}).catch(()=>{})}},bi=(async()=>process.platform==="linux"?({autoLaunchEnabled:await $naniGetAutostart()}):({autoLaunchEnabled:c.app.getLoginItemSettings().openAtLogin}))` + AUTOSTART_MARKER + ',Ai="ms-settings:startupapps";';
|
||||
|
||||
const AUTOSTART_SET_REPLACEMENT = 'const Mi=(async(e,t)=>{if(process.platform==="win32")return await vi();try{return process.platform==="linux"?await $naniSetAutostart(t):(c.app.setLoginItemSettings({openAtLogin:t,openAsHidden:!0}),{ok:!0})}catch(n){return{ok:!1,message:n instanceof Error?n.message:"Unknown error"}}})';
|
||||
|
||||
export default definePatch({
|
||||
id: "xdg-autostart",
|
||||
phase: PHASE_MAIN_BUNDLE,
|
||||
order: 30,
|
||||
required: false,
|
||||
apply({ source }) {
|
||||
const status = markerStatus(source, AUTOSTART_MARKER);
|
||||
if (status) return { source, status };
|
||||
let patched = replaceExactly(
|
||||
source,
|
||||
AUTOSTART_GET_ANCHOR,
|
||||
AUTOSTART_GET_REPLACEMENT,
|
||||
"XDG autostart getter",
|
||||
);
|
||||
patched = replaceExactly(
|
||||
patched,
|
||||
AUTOSTART_SET_ANCHOR,
|
||||
AUTOSTART_SET_REPLACEMENT,
|
||||
"XDG autostart setter",
|
||||
);
|
||||
return { source: patched, status: "applied" };
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user