From f90ef10b78722e385ceb733a9d23bd8e1ce86bdd Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 2 Aug 2026 01:49:37 +0100 Subject: [PATCH] Refactor Windows installer scripts and enhance Electron navigation - Added macros for uninstalling previous Farm Control installations in the NSIS installer. - Updated the MSI wrapped configuration to include custom actions for handling previous installations. - Improved the Electron navigation by modifying CSS classes for better drag-and-drop functionality. - Enhanced build scripts to include the WixUtilExtension for improved MSI packaging. --- assets/stylesheets/App.css | 10 +- packaging/windows/farmcontrol.nsi | 1 + packaging/windows/installer.nsh | 30 +++ packaging/windows/msi-wrapped.wxs | 61 ++++- scripts/build-windows-msi.ps1 | 4 +- scripts/patch-electrobun-src.mjs | 166 ++++++------ .../Dashboard/common/DashboardNavigation.jsx | 240 +++++++++--------- .../common/DashboardWindowButtons.jsx | 5 +- 8 files changed, 308 insertions(+), 209 deletions(-) diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css index 2d2b8be..d03b350 100644 --- a/assets/stylesheets/App.css +++ b/assets/stylesheets/App.css @@ -104,14 +104,8 @@ line-height: 0.7; } -.electron-navigation-wrapper { - -webkit-app-region: drag; -} - -.electron-navigation-wrapper li, -.electron-navigation-wrapper button, -.electron-navigation-wrapper .ant-tag { - -webkit-app-region: no-drag; +.electron-navigation-wrapper.electrobun-webkit-app-region-drag { + user-select: none; } .electron-navigation { diff --git a/packaging/windows/farmcontrol.nsi b/packaging/windows/farmcontrol.nsi index ea49758..3fefc62 100644 --- a/packaging/windows/farmcontrol.nsi +++ b/packaging/windows/farmcontrol.nsi @@ -43,6 +43,7 @@ Function .onInit ${If} ${Silent} SetAutoClose true ${EndIf} + !insertmacro uninstallPreviousFarmControl FunctionEnd Section "Farm Control" SecMain diff --git a/packaging/windows/installer.nsh b/packaging/windows/installer.nsh index c32aa78..aec86f5 100644 --- a/packaging/windows/installer.nsh +++ b/packaging/windows/installer.nsh @@ -1,3 +1,33 @@ +!macro quitFarmControl + DetailPrint "Stopping running Farm Control processes..." + ExecWait 'taskkill /F /IM launcher.exe /T' $R0 +!macroend + +!macro uninstallPreviousFarmControl + ClearErrors + ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" "UninstallString" + StrCmp $R0 "" check_install_dir run_uninstall + + check_install_dir: + ReadRegStr $R0 HKLM "Software\Tom Butcher\Farm Control" "InstallDir" + StrCmp $R0 "" check_default_dir + StrCpy $R0 "$R0\Uninstall.exe" + Goto run_uninstall + + check_default_dir: + StrCpy $R0 "$PROGRAMFILES64\Farm Control\Uninstall.exe" + IfFileExists $R0 run_uninstall done_uninstall + + run_uninstall: + IfFileExists $R0 0 done_uninstall + DetailPrint "Removing previous Farm Control installation..." + !insertmacro quitFarmControl + ExecWait '$R0 /S' $R1 + DetailPrint "Previous installation removed (exit code: $R1)" + + done_uninstall: +!macroend + !macro createDesktopShortcut CreateShortCut "$DESKTOP\Farm Control.lnk" "$INSTDIR\bin\launcher.exe" !macroend diff --git a/packaging/windows/msi-wrapped.wxs b/packaging/windows/msi-wrapped.wxs index 348372b..014b897 100644 --- a/packaging/windows/msi-wrapped.wxs +++ b/packaging/windows/msi-wrapped.wxs @@ -23,8 +23,42 @@ + + + + + + + + + + + + + + + + + + - NOT Installed + NOT Installed + NOT Installed + NOT Installed AND PREVIOUS_UNINSTALL_CMD + NOT Installed AND NOT PREVIOUS_UNINSTALL_CMD AND PREVIOUS_INSTALL_DIR + NOT Installed AND (PREVIOUS_UNINSTALL_CMD OR PREVIOUS_INSTALL_DIR) + NOT Installed AND (PREVIOUS_UNINSTALL_CMD OR PREVIOUS_INSTALL_DIR) + NOT Installed AND (PREVIOUS_UNINSTALL_CMD OR PREVIOUS_INSTALL_DIR) + NOT Installed AND NOT PREVIOUS_UNINSTALL_CMD AND NOT PREVIOUS_INSTALL_DIR diff --git a/scripts/build-windows-msi.ps1 b/scripts/build-windows-msi.ps1 index 7080016..7ed0e6f 100644 --- a/scripts/build-windows-msi.ps1 +++ b/scripts/build-windows-msi.ps1 @@ -103,12 +103,12 @@ $wixObj = Join-Path $workDir "project.wixobj" Push-Location $workDir try { - & $wix.Candle "-arch" "x64" $projectWxs + & $wix.Candle "-arch" "x64" "-ext" "WixUtilExtension" $projectWxs if ($LASTEXITCODE -ne 0) { throw "candle.exe failed with exit code $LASTEXITCODE" } - & $wix.Light "-out" $outputMsiPath "-spdb" "-sw1076" $wixObj + & $wix.Light "-out" $outputMsiPath "-spdb" "-sw1076" "-ext" "WixUtilExtension" $wixObj if ($LASTEXITCODE -ne 0) { throw "light.exe failed with exit code $LASTEXITCODE" } diff --git a/scripts/patch-electrobun-src.mjs b/scripts/patch-electrobun-src.mjs index fcad615..3c75776 100644 --- a/scripts/patch-electrobun-src.mjs +++ b/scripts/patch-electrobun-src.mjs @@ -3,34 +3,34 @@ import { existsSync, mkdirSync, readFileSync, - writeFileSync, -} from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; + writeFileSync +} from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' -const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const electrobunDir = path.join(rootDir, "node_modules/electrobun"); -const sharedSrcDir = path.join(electrobunDir, "src/shared"); -const sharedDistDir = path.join(electrobunDir, "dist/api/shared"); +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const electrobunDir = path.join(rootDir, 'node_modules/electrobun') +const sharedSrcDir = path.join(electrobunDir, 'src/shared') +const sharedDistDir = path.join(electrobunDir, 'dist/api/shared') if (!existsSync(sharedDistDir)) { - console.error("patch-electrobun-src: electrobun dist/api/shared not found"); - process.exit(1); + console.error('patch-electrobun-src: electrobun dist/api/shared not found') + process.exit(1) } -mkdirSync(sharedSrcDir, { recursive: true }); +mkdirSync(sharedSrcDir, { recursive: true }) for (const fileName of [ - "cef-version.ts", - "bun-version.ts", - "electrobun-version.ts", - "naming.ts", - "rpc.ts", + 'cef-version.ts', + 'bun-version.ts', + 'electrobun-version.ts', + 'naming.ts', + 'rpc.ts' ]) { - const source = path.join(sharedDistDir, fileName); - const destination = path.join(sharedSrcDir, fileName); + const source = path.join(sharedDistDir, fileName) + const destination = path.join(sharedSrcDir, fileName) if (existsSync(source)) { - cpSync(source, destination); + cpSync(source, destination) } } @@ -109,14 +109,14 @@ export function getPlatformOS(): SupportedOS { export function getPlatformArch(): SupportedArch { return ARCH; } -`; +` -writeFileSync(path.join(sharedSrcDir, "platform.ts"), platformPatch); +writeFileSync(path.join(sharedSrcDir, 'platform.ts'), platformPatch) -const templatesDir = path.join(electrobunDir, "src/cli/templates"); -mkdirSync(templatesDir, { recursive: true }); +const templatesDir = path.join(electrobunDir, 'src/cli/templates') +mkdirSync(templatesDir, { recursive: true }) writeFileSync( - path.join(templatesDir, "embedded.ts"), + path.join(templatesDir, 'embedded.ts'), `export function getTemplateNames() { return []; } @@ -124,8 +124,8 @@ writeFileSync( export function getTemplate(name: string) { throw new Error(\`Template not available in patched electrobun CLI: \${name}\`); } -`, -); +` +) const RCEDIT_RESOLVER_FN = `function resolveProjectRceditPkgPath(projectRoot) { const candidates = [ @@ -140,35 +140,41 @@ const RCEDIT_RESOLVER_FN = `function resolveProjectRceditPkgPath(projectRoot) { throw new Error( "rcedit not found under " + projectRoot + ". Install rcedit in the project (bun add -d rcedit).", ); -}`; +}` -const cliPath = path.join(electrobunDir, "src/cli/index.ts"); -let cliSource = readFileSync(cliPath, "utf8"); -if (!cliSource.includes("HOST_ARCH")) { +const cliPath = path.join(electrobunDir, 'src/cli/index.ts') +let cliSource = readFileSync(cliPath, 'utf8') +if (!cliSource.includes('HOST_ARCH')) { cliSource = cliSource.replace( 'import { OS, ARCH } from "../shared/platform";', - 'import { OS, ARCH, HOST_ARCH } from "../shared/platform";', - ); + 'import { OS, ARCH, HOST_ARCH } from "../shared/platform";' + ) cliSource = cliSource.replace( - "const hostPaths = getPlatformPaths(OS, ARCH);", - "const hostPaths = getPlatformPaths(OS, HOST_ARCH);", - ); + 'const hostPaths = getPlatformPaths(OS, ARCH);', + 'const hostPaths = getPlatformPaths(OS, HOST_ARCH);' + ) } -if (!cliSource.includes("toolPaths")) { +if (!cliSource.includes('toolPaths')) { cliSource = cliSource.replace( - "const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);", - "const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);\n\t\tconst toolPaths = getPlatformPaths(currentTarget.os, ARCH !== HOST_ARCH ? HOST_ARCH : ARCH);", - ); - cliSource = cliSource.replaceAll("const zstdPath = targetPaths.ZSTD;", "const zstdPath = toolPaths.ZSTD;"); - cliSource = cliSource.replaceAll("const bsdiffpath = targetPaths.BSDIFF;", "const bsdiffpath = toolPaths.BSDIFF;"); + 'const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);', + 'const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);\n\t\tconst toolPaths = getPlatformPaths(currentTarget.os, ARCH !== HOST_ARCH ? HOST_ARCH : ARCH);' + ) + cliSource = cliSource.replaceAll( + 'const zstdPath = targetPaths.ZSTD;', + 'const zstdPath = toolPaths.ZSTD;' + ) + cliSource = cliSource.replaceAll( + 'const bsdiffpath = targetPaths.BSDIFF;', + 'const bsdiffpath = toolPaths.BSDIFF;' + ) cliSource = cliSource.replace( - "zigAsarCli = join(targetPaths.BSPATCH).replace(\"bspatch\", \"zig-asar\");", - "zigAsarCli = join(toolPaths.BSPATCH).replace(\"bspatch\", \"zig-asar\");", - ); - writeFileSync(cliPath, cliSource); - cliSource = readFileSync(cliPath, "utf8"); + 'zigAsarCli = join(targetPaths.BSPATCH).replace("bspatch", "zig-asar");', + 'zigAsarCli = join(toolPaths.BSPATCH).replace("bspatch", "zig-asar");' + ) + writeFileSync(cliPath, cliSource) + cliSource = readFileSync(cliPath, 'utf8') } -if (!cliSource.includes("hookBunBinary")) { +if (!cliSource.includes('hookBunBinary')) { cliSource = cliSource.replace( `const hostPaths = getPlatformPaths(OS, HOST_ARCH); @@ -178,41 +184,41 @@ if (!cliSource.includes("hookBunBinary")) { ? hostPaths.BUN_BINARY : process.execPath; - const result = Bun.spawnSync([hookBunBinary, hookScript],`, - ); + const result = Bun.spawnSync([hookBunBinary, hookScript],` + ) cliSource = cliSource.replace( - "console.error(\"Tried to run with bun at:\", hostPaths.BUN_BINARY);", - "console.error(\"Tried to run with bun at:\", hookBunBinary);", - ); - writeFileSync(cliPath, cliSource); - cliSource = readFileSync(cliPath, "utf8"); + 'console.error("Tried to run with bun at:", hostPaths.BUN_BINARY);', + 'console.error("Tried to run with bun at:", hookBunBinary);' + ) + writeFileSync(cliPath, cliSource) + cliSource = readFileSync(cliPath, 'utf8') } -if (!cliSource.includes("resolveProjectRceditPkgPath")) { +if (!cliSource.includes('resolveProjectRceditPkgPath')) { cliSource = cliSource.replaceAll( - "const rceditPkgPath = require.resolve(\"rcedit/package.json\");", - "const rceditPkgPath = resolveProjectRceditPkgPath(projectRoot);", - ); + 'const rceditPkgPath = require.resolve("rcedit/package.json");', + 'const rceditPkgPath = resolveProjectRceditPkgPath(projectRoot);' + ) cliSource = cliSource.replace( - "function getPlatformPaths(", + 'function getPlatformPaths(', `${RCEDIT_RESOLVER_FN} -function getPlatformPaths(`, - ); - writeFileSync(cliPath, cliSource); - cliSource = readFileSync(cliPath, "utf8"); +function getPlatformPaths(` + ) + writeFileSync(cliPath, cliSource) + cliSource = readFileSync(cliPath, 'utf8') } else { cliSource = cliSource.replace( /function resolveProjectRceditPkgPath\(projectRoot\) \{[\s\S]*?\n\}/m, - RCEDIT_RESOLVER_FN, - ); - writeFileSync(cliPath, cliSource); + RCEDIT_RESOLVER_FN + ) + writeFileSync(cliPath, cliSource) } -const electrobunCjsPath = path.join(electrobunDir, "bin/electrobun.cjs"); -let electrobunCjs = readFileSync(electrobunCjsPath, "utf8"); +const electrobunCjsPath = path.join(electrobunDir, 'bin/electrobun.cjs') +let electrobunCjs = readFileSync(electrobunCjsPath, 'utf8') -if (!electrobunCjs.includes("farmcontrol-use-patched-cli")) { +if (!electrobunCjs.includes('farmcontrol-use-patched-cli')) { electrobunCjs = electrobunCjs.replace( `async function main() { try { @@ -259,19 +265,21 @@ if (!electrobunCjs.includes("farmcontrol-use-patched-cli")) { const child = spawn(cliPath, args, { stdio: 'inherit', cwd: process.cwd() - });`, - ); - writeFileSync(electrobunCjsPath, electrobunCjs); + });` + ) + writeFileSync(electrobunCjsPath, electrobunCjs) } -const rceditSrc = path.join(rootDir, "node_modules/rcedit"); -const rceditDest = path.join(electrobunDir, "node_modules/rcedit"); +const rceditSrc = path.join(rootDir, 'node_modules/rcedit') +const rceditDest = path.join(electrobunDir, 'node_modules/rcedit') if (existsSync(rceditSrc)) { - mkdirSync(path.join(electrobunDir, "node_modules"), { recursive: true }); - cpSync(rceditSrc, rceditDest, { recursive: true, force: true }); + mkdirSync(path.join(electrobunDir, 'node_modules'), { recursive: true }) + cpSync(rceditSrc, rceditDest, { recursive: true, force: true }) } -const markerPath = path.join(electrobunDir, ".farmcontrol-electrobun-patched"); -writeFileSync(markerPath, `patched-at=${new Date().toISOString()}\n`); +const markerPath = path.join(electrobunDir, '.farmcontrol-electrobun-patched') +writeFileSync(markerPath, `patched-at=${new Date().toISOString()}\n`) -console.log("patch-electrobun-src: electrobun src/shared ready for cross-arch builds"); +console.log( + 'patch-electrobun-src: electrobun src/shared ready for cross-arch builds' +) diff --git a/src/components/Dashboard/common/DashboardNavigation.jsx b/src/components/Dashboard/common/DashboardNavigation.jsx index e621ccb..dc1f14c 100644 --- a/src/components/Dashboard/common/DashboardNavigation.jsx +++ b/src/components/Dashboard/common/DashboardNavigation.jsx @@ -199,134 +199,138 @@ const DashboardNavigation = () => { )} - } - style={{ marginBottom: '4px' }} - /> - } - /> - {isMobile &&
} - - - - showSpotlight()} - > +
+ } type='text' - style={{ marginTop: '4px' }} - onClick={() => showSpotlight()} + icon={} + style={{ marginBottom: '4px' }} /> - - + } + /> +
+ {isMobile &&
} +
+ + + toggleNotificationCenter()} + shortcut='alt+q' + hint='ALT Q' + onTrigger={() => showSpotlight()} >
{isOtherApp ? : null} ) @@ -335,7 +339,7 @@ const DashboardNavigation = () => { <> {isElectron ? ( {navigationContents} diff --git a/src/components/Dashboard/common/DashboardWindowButtons.jsx b/src/components/Dashboard/common/DashboardWindowButtons.jsx index d5f6826..3a6b40e 100644 --- a/src/components/Dashboard/common/DashboardWindowButtons.jsx +++ b/src/components/Dashboard/common/DashboardWindowButtons.jsx @@ -39,7 +39,10 @@ const DashboardWindowButtons = () => {
) : null ) : ( -
+
{maximizeButton} {minimizeButton}