Enhance Windows installer and application path handling
- Added a macro to fix the working directory for shortcuts in the Windows installer, ensuring correct execution context. - Updated shortcut creation logic in `installer.nsh` to utilize the new macro for both desktop and start menu shortcuts. - Implemented a function to patch the native wrapper path in the Electrobun source, improving compatibility with different execution contexts. - Adjusted the `deeplink.js` and `index.js` files to set the current working directory appropriately, enhancing application behavior on Windows. - Introduced a new module for managing window work area constraints, improving window management and user experience on Windows platforms.
This commit is contained in:
parent
17b5068644
commit
5c1ea30e5d
@ -28,15 +28,40 @@
|
|||||||
done_uninstall:
|
done_uninstall:
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
|
!macro fixShortcutWorkingDir SHORTCUT_PATH WORKING_DIR
|
||||||
|
Push $0
|
||||||
|
Push $1
|
||||||
|
Push $2
|
||||||
|
StrCpy $1 "${SHORTCUT_PATH}"
|
||||||
|
StrCpy $2 "${WORKING_DIR}"
|
||||||
|
InitPluginsDir
|
||||||
|
FileOpen $0 "$PLUGINSDIR\fix-shortcut.ps1" w
|
||||||
|
FileWrite $0 '$$s = (New-Object -COM WScript.Shell).CreateShortcut("'
|
||||||
|
FileWrite $0 $1
|
||||||
|
FileWrite $0 '")$\r$\n'
|
||||||
|
FileWrite $0 '$$s.WorkingDirectory = "'
|
||||||
|
FileWrite $0 $2
|
||||||
|
FileWrite $0 '"$\r$\n'
|
||||||
|
FileWrite $0 '$$s.Save()$\r$\n'
|
||||||
|
FileClose $0
|
||||||
|
ExecWait '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "$PLUGINSDIR\fix-shortcut.ps1"'
|
||||||
|
Delete "$PLUGINSDIR\fix-shortcut.ps1"
|
||||||
|
Pop $2
|
||||||
|
Pop $1
|
||||||
|
Pop $0
|
||||||
|
!macroend
|
||||||
|
|
||||||
!macro createDesktopShortcut
|
!macro createDesktopShortcut
|
||||||
SetShellVarContext all
|
SetShellVarContext all
|
||||||
CreateShortCut "$DESKTOP\Farm Control.lnk" "$INSTDIR\bin\launcher.exe"
|
CreateShortCut "$DESKTOP\Farm Control.lnk" "$INSTDIR\bin\launcher.exe" "" "$INSTDIR\bin\launcher.exe" 0 SW_SHOWNORMAL "" "Farm Control"
|
||||||
|
!insertmacro fixShortcutWorkingDir "$DESKTOP\Farm Control.lnk" "$INSTDIR\bin"
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
!macro createStartMenuShortcut
|
!macro createStartMenuShortcut
|
||||||
SetShellVarContext all
|
SetShellVarContext all
|
||||||
CreateDirectory "$SMPROGRAMS\Farm Control"
|
CreateDirectory "$SMPROGRAMS\Farm Control"
|
||||||
CreateShortCut "$SMPROGRAMS\Farm Control\Farm Control.lnk" "$INSTDIR\bin\launcher.exe"
|
CreateShortCut "$SMPROGRAMS\Farm Control\Farm Control.lnk" "$INSTDIR\bin\launcher.exe" "" "$INSTDIR\bin\launcher.exe" 0 SW_SHOWNORMAL "" "Farm Control"
|
||||||
|
!insertmacro fixShortcutWorkingDir "$SMPROGRAMS\Farm Control\Farm Control.lnk" "$INSTDIR\bin"
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
!macro removeDesktopShortcut
|
!macro removeDesktopShortcut
|
||||||
|
|||||||
@ -277,6 +277,38 @@ if (existsSync(rceditSrc)) {
|
|||||||
cpSync(rceditSrc, rceditDest, { recursive: true, force: true })
|
cpSync(rceditSrc, rceditDest, { recursive: true, force: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function patchNativeWrapperPath(nativeTsPath) {
|
||||||
|
if (!existsSync(nativeTsPath)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let source = readFileSync(nativeTsPath, 'utf8')
|
||||||
|
const needle =
|
||||||
|
'const nativeWrapperPath = join(process.cwd(), `libNativeWrapper.${suffix}`);'
|
||||||
|
const replacement =
|
||||||
|
'const nativeWrapperPath = join(dirname(process.execPath), `libNativeWrapper.${suffix}`);'
|
||||||
|
|
||||||
|
if (!source.includes(needle)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source.includes('dirname')) {
|
||||||
|
source = source.replace(
|
||||||
|
'import { join } from "path";',
|
||||||
|
'import { dirname, join } from "path";'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
source = source.replace(needle, replacement)
|
||||||
|
writeFileSync(nativeTsPath, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const distDir of ['dist', 'dist-macos-arm64', 'dist-win-x64']) {
|
||||||
|
patchNativeWrapperPath(
|
||||||
|
path.join(electrobunDir, distDir, 'api/bun/proc/native.ts')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const markerPath = path.join(electrobunDir, '.farmcontrol-electrobun-patched')
|
const markerPath = path.join(electrobunDir, '.farmcontrol-electrobun-patched')
|
||||||
writeFileSync(markerPath, `patched-at=${new Date().toISOString()}\n`)
|
writeFileSync(markerPath, `patched-at=${new Date().toISOString()}\n`)
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@ if (!existsSync(launcherPath)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const child = spawn(launcherPath, [], {
|
const child = spawn(launcherPath, [], {
|
||||||
|
cwd: dirname(launcherPath),
|
||||||
detached: true,
|
detached: true,
|
||||||
stdio: 'ignore',
|
stdio: 'ignore',
|
||||||
windowsHide: true
|
windowsHide: true
|
||||||
|
|||||||
@ -1,9 +1,19 @@
|
|||||||
|
import { chdirSync } from 'node:fs'
|
||||||
|
import { dirname } from 'node:path'
|
||||||
import {
|
import {
|
||||||
captureLaunchUrl,
|
captureLaunchUrl,
|
||||||
closeSingleInstanceServer,
|
closeSingleInstanceServer,
|
||||||
ensureSingleInstanceLock
|
ensureSingleInstanceLock
|
||||||
} from '../desktop/single-instance.js'
|
} from '../desktop/single-instance.js'
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
try {
|
||||||
|
chdirSync(dirname(process.execPath))
|
||||||
|
} catch {
|
||||||
|
// Best effort; some launchers may not allow changing directory.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const launchUrl = captureLaunchUrl()
|
const launchUrl = captureLaunchUrl()
|
||||||
const gotSingleInstanceLock = await ensureSingleInstanceLock({ launchUrl })
|
const gotSingleInstanceLock = await ensureSingleInstanceLock({ launchUrl })
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,10 @@ import {
|
|||||||
MAC_TRAFFIC_LIGHT_OFFSET
|
MAC_TRAFFIC_LIGHT_OFFSET
|
||||||
} from './macos-window-effects.js'
|
} from './macos-window-effects.js'
|
||||||
import { sendToRenderer, setMessageSender } from './notify.js'
|
import { sendToRenderer, setMessageSender } from './notify.js'
|
||||||
|
import {
|
||||||
|
clampWindowToWorkArea,
|
||||||
|
isWindowWorkAreaMaximized
|
||||||
|
} from './windows-work-area.js'
|
||||||
|
|
||||||
const isMacOS = process.platform === 'darwin'
|
const isMacOS = process.platform === 'darwin'
|
||||||
const isWindows = process.platform === 'win32'
|
const isWindows = process.platform === 'win32'
|
||||||
@ -167,12 +171,20 @@ function syncWindowsWebviewLayout(window) {
|
|||||||
window.setSize(width, height)
|
window.setSize(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleWindowsWindowChange(window) {
|
||||||
|
if (clampWindowToWorkArea(window)) {
|
||||||
|
syncWindowsWebviewLayout(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastWindowState()
|
||||||
|
}
|
||||||
|
|
||||||
function applyWindowsStartupWindowState(window) {
|
function applyWindowsStartupWindowState(window) {
|
||||||
if (!window) return
|
if (!window) return
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.maximize?.()
|
window.maximize?.()
|
||||||
syncWindowsWebviewLayout(window)
|
handleWindowsWindowChange(window)
|
||||||
setTimeout(() => syncWindowsWebviewLayout(window), 0)
|
setTimeout(() => syncWindowsWebviewLayout(window), 0)
|
||||||
setTimeout(() => syncWindowsWebviewLayout(window), 100)
|
setTimeout(() => syncWindowsWebviewLayout(window), 100)
|
||||||
setTimeout(broadcastWindowState, 100)
|
setTimeout(broadcastWindowState, 100)
|
||||||
@ -181,9 +193,13 @@ function applyWindowsStartupWindowState(window) {
|
|||||||
|
|
||||||
function setupWindowEvents(window) {
|
function setupWindowEvents(window) {
|
||||||
// Electrobun emits resize/focus, not Electron's maximize/fullscreen events.
|
// Electrobun emits resize/focus, not Electron's maximize/fullscreen events.
|
||||||
window.on?.('resize', broadcastWindowState)
|
const onWindowChange = isWindows
|
||||||
|
? () => handleWindowsWindowChange(window)
|
||||||
|
: broadcastWindowState
|
||||||
|
|
||||||
|
window.on?.('resize', onWindowChange)
|
||||||
window.on?.('focus', broadcastWindowState)
|
window.on?.('focus', broadcastWindowState)
|
||||||
window.on?.('move', broadcastWindowState)
|
window.on?.('move', onWindowChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupMainWindowMessaging(window = mainWindow) {
|
export function setupMainWindowMessaging(window = mainWindow) {
|
||||||
@ -314,7 +330,9 @@ export function getWindowState() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isFullScreen: mainWindow.isFullScreen?.() ?? false,
|
isFullScreen: mainWindow.isFullScreen?.() ?? false,
|
||||||
isMaximized: mainWindow.isMaximized?.() ?? false
|
isMaximized: isWindows
|
||||||
|
? isWindowWorkAreaMaximized(mainWindow)
|
||||||
|
: (mainWindow.isMaximized?.() ?? false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,6 +349,10 @@ export function handleWindowControl(action) {
|
|||||||
} else {
|
} else {
|
||||||
mainWindow.maximize?.()
|
mainWindow.maximize?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isWindows) {
|
||||||
|
handleWindowsWindowChange(mainWindow)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case 'fullscreen':
|
case 'fullscreen':
|
||||||
if (mainWindow.isFullScreen?.()) {
|
if (mainWindow.isFullScreen?.()) {
|
||||||
|
|||||||
83
src/desktop/windows-work-area.js
Normal file
83
src/desktop/windows-work-area.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { Screen } from 'electrobun/bun'
|
||||||
|
|
||||||
|
const FRAME_TOLERANCE_PX = 4
|
||||||
|
|
||||||
|
function framesMatch(a, b, tolerance = FRAME_TOLERANCE_PX) {
|
||||||
|
return (
|
||||||
|
Math.abs(a.x - b.x) <= tolerance &&
|
||||||
|
Math.abs(a.y - b.y) <= tolerance &&
|
||||||
|
Math.abs(a.width - b.width) <= tolerance &&
|
||||||
|
Math.abs(a.height - b.height) <= tolerance
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFrameCenter(frame) {
|
||||||
|
return {
|
||||||
|
x: frame.x + frame.width / 2,
|
||||||
|
y: frame.y + frame.height / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayForFrame(frame) {
|
||||||
|
const { x, y } = getFrameCenter(frame)
|
||||||
|
const displays = Screen.getAllDisplays()
|
||||||
|
|
||||||
|
const match = displays.find(({ bounds }) => {
|
||||||
|
return (
|
||||||
|
x >= bounds.x &&
|
||||||
|
x < bounds.x + bounds.width &&
|
||||||
|
y >= bounds.y &&
|
||||||
|
y < bounds.y + bounds.height
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return match ?? Screen.getPrimaryDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
function frameCoversMonitor(frame, bounds) {
|
||||||
|
return (
|
||||||
|
frame.x <= bounds.x + FRAME_TOLERANCE_PX &&
|
||||||
|
frame.y <= bounds.y + FRAME_TOLERANCE_PX &&
|
||||||
|
frame.x + frame.width >= bounds.x + bounds.width - FRAME_TOLERANCE_PX &&
|
||||||
|
frame.y + frame.height >= bounds.y + bounds.height - FRAME_TOLERANCE_PX
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWindowWorkAreaMaximized(window) {
|
||||||
|
if (!window?.getFrame) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const frame = window.getFrame()
|
||||||
|
const { workArea } = getDisplayForFrame(frame)
|
||||||
|
return framesMatch(frame, workArea)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clampWindowToWorkArea(window) {
|
||||||
|
if (!window?.getFrame || !window?.setFrame) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.isFullScreen?.()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const frame = window.getFrame()
|
||||||
|
if (!frame.width || !frame.height) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const display = getDisplayForFrame(frame)
|
||||||
|
const { bounds, workArea } = display
|
||||||
|
|
||||||
|
if (framesMatch(frame, workArea)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frameCoversMonitor(frame, bounds)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setFrame(workArea.x, workArea.y, workArea.width, workArea.height)
|
||||||
|
return true
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user