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()}
>
}
+ icon={}
type='text'
- style={{ marginTop: '2px' }}
- onClick={() => toggleNotificationCenter()}
+ style={{ marginTop: '4px' }}
+ onClick={() => showSpotlight()}
/>
-
-
- {import.meta.env.MODE === 'development' && (
-
- {apiServerState === 'connected' ? (
-
- }
- />
-
- ) : null}
- {apiServerState === 'connecting' ? (
-
- }
- />
-
- ) : null}
- {apiServerState === 'disconnected' ? (
-
- }
- />
-
- ) : null}
-
- }
- onClick={() => {
- navigate('/dashboard/developer/sessionstorage')
- }}
- />
-
-
- )}
- {userProfile ? (
-
-
- }>
- {!isMobile && (userProfile?.name || userProfile.username)}
-
-
+ toggleNotificationCenter()}
+ >
+ }
+ type='text'
+ style={{ marginTop: '2px' }}
+ onClick={() => toggleNotificationCenter()}
+ />
+
+
- ) : null}
- {isElectron && availableUpdate ? (
- }
- style={{ cursor: 'pointer', margin: '2px 0 0 0' }}
- color='cyan'
- onClick={() => checkForUpdates()}
- >
- Update Available
-
- ) : null}
-
+ {import.meta.env.MODE === 'development' && (
+
+ {apiServerState === 'connected' ? (
+
+ }
+ />
+
+ ) : null}
+ {apiServerState === 'connecting' ? (
+
+ }
+ />
+
+ ) : null}
+ {apiServerState === 'disconnected' ? (
+
+ }
+ />
+
+ ) : null}
+
+ }
+ onClick={() => {
+ navigate('/dashboard/developer/sessionstorage')
+ }}
+ />
+
+
+ )}
+ {userProfile ? (
+
+
+ }>
+ {!isMobile && (userProfile?.name || userProfile.username)}
+
+
+
+ ) : null}
+ {isElectron && availableUpdate ? (
+
}
+ style={{ cursor: 'pointer', margin: '2px 0 0 0' }}
+ color='cyan'
+ onClick={() => checkForUpdates()}
+ >
+ Update Available
+
+ ) : null}
+
+
{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}