Enhance macOS window effects validation and build process
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
- Introduced functions to validate the macOS dynamic library, ensuring it meets size and architecture requirements. - Updated build scripts to resolve target architecture dynamically and validate the generated dylib. - Enhanced the macOS window effects implementation to include traffic light positioning and improved error handling. - Refactored the dylib path resolution logic to support multiple potential locations for the library.
This commit is contained in:
parent
8d9455eaa9
commit
a8339aa9f8
@ -5,7 +5,27 @@ import { fileURLToPath } from "node:url";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const srcFile = path.join(rootDir, "native/macos/window-effects.mm");
|
||||
const outFile = path.join(rootDir, "src/bun/libMacWindowEffects.dylib");
|
||||
export const outFile = path.join(rootDir, "src/bun/libMacWindowEffects.dylib");
|
||||
const minDylibBytes = 10_000;
|
||||
|
||||
function normalizeArch(value) {
|
||||
if (value === "x64" || value === "amd64" || value === "x86_64") {
|
||||
return "x86_64";
|
||||
}
|
||||
if (value === "arm64" || value === "aarch64") {
|
||||
return "arm64";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveTargetArch() {
|
||||
return (
|
||||
normalizeArch(process.env.ELECTROBUN_TARGET_ARCH) ||
|
||||
normalizeArch(process.env.ELECTROBUN_ARCH) ||
|
||||
normalizeArch(process.arch) ||
|
||||
"arm64"
|
||||
);
|
||||
}
|
||||
|
||||
function createPlaceholder() {
|
||||
mkdirSync(path.dirname(outFile), { recursive: true });
|
||||
@ -13,6 +33,49 @@ function createPlaceholder() {
|
||||
console.log(`build-macos-effects: created placeholder dylib at ${outFile}`);
|
||||
}
|
||||
|
||||
function readDylibArch(dylibPath) {
|
||||
const lipo = spawnSync("lipo", ["-info", dylibPath], { encoding: "utf8" });
|
||||
if (lipo.status === 0) {
|
||||
return lipo.stdout;
|
||||
}
|
||||
|
||||
const file = spawnSync("file", ["-b", dylibPath], { encoding: "utf8" });
|
||||
return file.stdout || "";
|
||||
}
|
||||
|
||||
export function validateMacosEffectsDylib({
|
||||
dylibPath = outFile,
|
||||
expectedArch = resolveTargetArch(),
|
||||
required = process.platform === "darwin",
|
||||
} = {}) {
|
||||
if (!required) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!existsSync(dylibPath)) {
|
||||
console.error(`build-macos-effects: missing dylib at ${dylibPath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const { size } = statSync(dylibPath);
|
||||
if (size < minDylibBytes) {
|
||||
console.error(
|
||||
`build-macos-effects: dylib at ${dylibPath} is too small (${size} bytes)`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const archInfo = readDylibArch(dylibPath);
|
||||
if (!archInfo.includes(expectedArch)) {
|
||||
console.error(
|
||||
`build-macos-effects: dylib architecture mismatch (expected ${expectedArch}): ${archInfo.trim()}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (process.platform !== "darwin") {
|
||||
createPlaceholder();
|
||||
process.exit(0);
|
||||
@ -25,12 +88,16 @@ if (!existsSync(srcFile)) {
|
||||
|
||||
mkdirSync(path.dirname(outFile), { recursive: true });
|
||||
|
||||
const targetArch = resolveTargetArch();
|
||||
const result = spawnSync(
|
||||
"xcrun",
|
||||
[
|
||||
"clang++",
|
||||
"-dynamiclib",
|
||||
"-fobjc-arc",
|
||||
"-arch",
|
||||
targetArch,
|
||||
"-mmacosx-version-min=11.0",
|
||||
"-framework",
|
||||
"Cocoa",
|
||||
srcFile,
|
||||
@ -44,5 +111,11 @@ if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
|
||||
if (!validateMacosEffectsDylib({ dylibPath: outFile, expectedArch: targetArch })) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { size } = statSync(outFile);
|
||||
console.log(`build-macos-effects: built ${outFile} (${size} bytes)`);
|
||||
console.log(
|
||||
`build-macos-effects: built ${outFile} (${size} bytes, arch=${targetArch})`,
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const buildEnv = process.env.ELECTROBUN_BUILD_ENV || "dev";
|
||||
@ -46,6 +46,21 @@ if (buildMacosEffects.status !== 0) {
|
||||
process.exit(buildMacosEffects.status ?? 1);
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
const { validateMacosEffectsDylib, outFile } = await import(
|
||||
pathToFileURL(
|
||||
path.join(rootDir, "scripts/build-macos-effects.mjs"),
|
||||
).href
|
||||
);
|
||||
|
||||
if (!validateMacosEffectsDylib({ dylibPath: outFile })) {
|
||||
console.error(
|
||||
"pre-build: macOS window effects dylib is missing or invalid; blur and traffic lights will not work in packaged builds.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const prepareIcons = spawnSync(
|
||||
"bun",
|
||||
[
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { getReleaseArch } from "./release-artifact-utils.mjs";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
@ -36,6 +36,21 @@ if (macosEffectsResult.status !== 0) {
|
||||
process.exit(macosEffectsResult.status ?? 1);
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
const { validateMacosEffectsDylib, outFile } = await import(
|
||||
pathToFileURL(
|
||||
path.join(rootDir, "scripts/build-macos-effects.mjs"),
|
||||
).href
|
||||
);
|
||||
|
||||
if (!validateMacosEffectsDylib({ dylibPath: outFile })) {
|
||||
console.error(
|
||||
"run-electrobun-build: macOS window effects dylib is missing or invalid.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const ensureCoreEnv = {
|
||||
...process.env,
|
||||
ELECTROBUN_TARGET_ARCH: targetArch,
|
||||
|
||||
@ -4,22 +4,38 @@ import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
export const MAC_WINDOW_CORNER_RADIUS = 15
|
||||
export const MAC_TRAFFIC_LIGHT_OFFSET = { x: 14, y: 12 }
|
||||
|
||||
const DYLIB_NAME = 'libMacWindowEffects.dylib'
|
||||
const MIN_DYLIB_BYTES = 10_000
|
||||
|
||||
function resolveDylibPath() {
|
||||
const candidates = new Set()
|
||||
const moduleDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
const candidates = [
|
||||
path.join(moduleDir, '../bun/libMacWindowEffects.dylib'),
|
||||
path.join(moduleDir, 'libMacWindowEffects.dylib')
|
||||
]
|
||||
|
||||
candidates.add(path.join(moduleDir, DYLIB_NAME))
|
||||
candidates.add(path.join(moduleDir, '../bun', DYLIB_NAME))
|
||||
candidates.add(path.join(moduleDir, '../../bun', DYLIB_NAME))
|
||||
candidates.add(path.join(process.cwd(), 'src/bun', DYLIB_NAME))
|
||||
|
||||
for (const execPath of [process.execPath, process.argv0].filter(Boolean)) {
|
||||
const execDir = path.dirname(execPath)
|
||||
candidates.add(
|
||||
path.join(execDir, '../Resources/app/bun', DYLIB_NAME)
|
||||
)
|
||||
candidates.add(path.join(execDir, 'Resources/app/bun', DYLIB_NAME))
|
||||
candidates.add(path.join(execDir, DYLIB_NAME))
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (!existsSync(candidate)) {
|
||||
const resolved = path.resolve(candidate)
|
||||
if (!existsSync(resolved)) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
if (statSync(candidate).size > 0) {
|
||||
return candidate
|
||||
if (statSync(resolved).size >= MIN_DYLIB_BYTES) {
|
||||
return resolved
|
||||
}
|
||||
} catch {
|
||||
// Ignore unreadable paths and keep searching.
|
||||
@ -29,21 +45,19 @@ function resolveDylibPath() {
|
||||
return null
|
||||
}
|
||||
|
||||
export function applyMacOSWindowEffects(mainWindow) {
|
||||
if (process.platform !== 'darwin' || !mainWindow?.ptr) {
|
||||
return
|
||||
}
|
||||
|
||||
function loadMacWindowEffectsLibrary() {
|
||||
const dylibPath = resolveDylibPath()
|
||||
if (!dylibPath) {
|
||||
console.warn(
|
||||
'macOS vibrancy: native effects library not found; using transparent window only.'
|
||||
)
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const lib = dlopen(dylibPath, {
|
||||
return {
|
||||
path: dylibPath,
|
||||
lib: dlopen(dylibPath, {
|
||||
enableWindowVibrancy: {
|
||||
args: [FFIType.ptr, FFIType.f64],
|
||||
returns: FFIType.bool
|
||||
@ -55,24 +69,75 @@ export function applyMacOSWindowEffects(mainWindow) {
|
||||
setWindowCornerRadius: {
|
||||
args: [FFIType.ptr, FFIType.f64],
|
||||
returns: FFIType.bool
|
||||
},
|
||||
setWindowTrafficLightsPosition: {
|
||||
args: [FFIType.ptr, FFIType.f64, FFIType.f64],
|
||||
returns: FFIType.bool
|
||||
},
|
||||
setNativeWindowDragRegion: {
|
||||
args: [FFIType.ptr, FFIType.f64, FFIType.f64],
|
||||
returns: FFIType.bool
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`macOS vibrancy: failed to load native effects library (${dylibPath}):`,
|
||||
error
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function applyTrafficLightsPosition(lib, mainWindow, attempt = 0) {
|
||||
const applied = lib.symbols.setWindowTrafficLightsPosition(
|
||||
mainWindow.ptr,
|
||||
MAC_TRAFFIC_LIGHT_OFFSET.x,
|
||||
MAC_TRAFFIC_LIGHT_OFFSET.y
|
||||
)
|
||||
|
||||
if (applied || attempt >= 10) {
|
||||
return applied
|
||||
}
|
||||
|
||||
setTimeout(() => applyTrafficLightsPosition(lib, mainWindow, attempt + 1), 50)
|
||||
return false
|
||||
}
|
||||
|
||||
export function applyMacOSWindowEffects(mainWindow) {
|
||||
if (process.platform !== 'darwin' || !mainWindow?.ptr) {
|
||||
return
|
||||
}
|
||||
|
||||
const loaded = loadMacWindowEffectsLibrary()
|
||||
if (!loaded) {
|
||||
return
|
||||
}
|
||||
|
||||
const { path: dylibPath, lib } = loaded
|
||||
|
||||
try {
|
||||
const vibrancyEnabled = lib.symbols.enableWindowVibrancy(
|
||||
mainWindow.ptr,
|
||||
MAC_WINDOW_CORNER_RADIUS
|
||||
)
|
||||
const shadowEnabled = lib.symbols.ensureWindowShadow(mainWindow.ptr)
|
||||
const trafficLightsScheduled = applyTrafficLightsPosition(lib, mainWindow)
|
||||
|
||||
mainWindow.on?.('resize', () => {
|
||||
lib.symbols.setWindowCornerRadius(
|
||||
mainWindow.ptr,
|
||||
MAC_WINDOW_CORNER_RADIUS
|
||||
)
|
||||
lib.symbols.setWindowTrafficLightsPosition(
|
||||
mainWindow.ptr,
|
||||
MAC_TRAFFIC_LIGHT_OFFSET.x,
|
||||
MAC_TRAFFIC_LIGHT_OFFSET.y
|
||||
)
|
||||
})
|
||||
|
||||
console.log(
|
||||
`macOS vibrancy applied (vibrancy=${vibrancyEnabled}, shadow=${shadowEnabled}, cornerRadius=${MAC_WINDOW_CORNER_RADIUS})`
|
||||
`macOS vibrancy applied (dylib=${dylibPath}, vibrancy=${vibrancyEnabled}, shadow=${shadowEnabled}, trafficLights=${trafficLightsScheduled}, cornerRadius=${MAC_WINDOW_CORNER_RADIUS})`
|
||||
)
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import Electrobun, { BrowserWindow, Updater, Utils } from 'electrobun/bun'
|
||||
import { applyApplicationMenu, setupApplicationMenuEvents } from './menu.js'
|
||||
import { applyMacOSWindowEffects } from './macos-window-effects.js'
|
||||
import {
|
||||
applyMacOSWindowEffects,
|
||||
MAC_TRAFFIC_LIGHT_OFFSET
|
||||
} from './macos-window-effects.js'
|
||||
import { sendToRenderer, setMessageSender } from './notify.js'
|
||||
|
||||
const isMacOS = process.platform === 'darwin'
|
||||
@ -137,7 +140,9 @@ export async function createMainWindow(rpc) {
|
||||
url,
|
||||
rpc,
|
||||
titleBarStyle: 'hiddenInset',
|
||||
...(isMacOS ? { transparent: true } : {}),
|
||||
...(isMacOS
|
||||
? { transparent: true, trafficLightOffset: MAC_TRAFFIC_LIGHT_OFFSET }
|
||||
: {}),
|
||||
frame: {
|
||||
width: 1200,
|
||||
height: 800,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user