Compare commits
No commits in common. "274852b895e5d0aaa25856bef85b663542dd34ad" and "37fe489b07cfd27bfdf48976918f52385bd44c62" have entirely different histories.
274852b895
...
37fe489b07
5
.gitignore
vendored
5
.gitignore
vendored
@ -31,7 +31,4 @@ stats.html
|
|||||||
|
|
||||||
test-results.xml
|
test-results.xml
|
||||||
|
|
||||||
dist/*
|
dist/*
|
||||||
|
|
||||||
# Native macOS vibrancy (built by scripts/build-macos-effects.mjs)
|
|
||||||
src/bun/libMacWindowEffects.dylib
|
|
||||||
@ -104,45 +104,14 @@
|
|||||||
line-height: 0.7;
|
line-height: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.electron-navigation-wrapper.electrobun-webkit-app-region-drag {
|
.electron-navigation-wrapper {
|
||||||
user-select: none;
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Native macOS vibrancy — transparent web content over NSVisualEffectView */
|
.electron-navigation-wrapper li,
|
||||||
html.macos-vibrancy {
|
.electron-navigation-wrapper button,
|
||||||
--macos-window-corner-radius: 15px; /* keep in sync with MAC_WINDOW_CORNER_RADIUS */
|
.electron-navigation-wrapper .ant-tag {
|
||||||
}
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
html.macos-vibrancy,
|
|
||||||
html.macos-vibrancy body,
|
|
||||||
html.macos-vibrancy #root {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.macos-vibrancy .ant-layout.ant-layout-has-sider,
|
|
||||||
html.macos-vibrancy .main-layout {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.macos-vibrancy .light-mode .ant-layout-sider {
|
|
||||||
background: rgba(255, 255, 255, 0.75) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.macos-vibrancy .dark-mode .ant-layout-sider {
|
|
||||||
background: rgba(10, 10, 10, 0.65) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.macos-vibrancy .light-mode .ant-layout-content {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.macos-vibrancy .dark-mode .ant-layout-content {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.macos-vibrancy .ant-layout-sider-light,
|
|
||||||
html.macos-vibrancy .electron-sider .ant-menu-light {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.electron-navigation {
|
.electron-navigation {
|
||||||
|
|||||||
@ -42,7 +42,6 @@ export default {
|
|||||||
},
|
},
|
||||||
copy: {
|
copy: {
|
||||||
"dist/mainview": "views/mainview",
|
"dist/mainview": "views/mainview",
|
||||||
"src/bun/libMacWindowEffects.dylib": "bun/libMacWindowEffects.dylib",
|
|
||||||
},
|
},
|
||||||
watch: ["scripts", "src"],
|
watch: ["scripts", "src"],
|
||||||
watchIgnore: ["dist/**", "build/**", "app_dist/**"],
|
watchIgnore: ["dist/**", "build/**", "app_dist/**"],
|
||||||
|
|||||||
@ -1,402 +0,0 @@
|
|||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
static NSString *const kElectrobunVibrancyViewIdentifier =
|
|
||||||
@"ElectrobunVibrancyView";
|
|
||||||
static NSString *const kElectrobunNativeDragViewIdentifier =
|
|
||||||
@"ElectrobunNativeDragView";
|
|
||||||
static NSString *const kElectrobunWindowBorderViewIdentifier =
|
|
||||||
@"ElectrobunWindowBorderView";
|
|
||||||
|
|
||||||
static CGFloat gWindowCornerRadius = 15.0;
|
|
||||||
|
|
||||||
@interface ElectrobunWindowBorderView : NSView
|
|
||||||
@property(nonatomic) CGFloat cornerRadius;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation ElectrobunWindowBorderView
|
|
||||||
@synthesize cornerRadius = _cornerRadius;
|
|
||||||
|
|
||||||
- (instancetype)initWithFrame:(NSRect)frameRect {
|
|
||||||
self = [super initWithFrame:frameRect];
|
|
||||||
if (self) {
|
|
||||||
_cornerRadius = gWindowCornerRadius;
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)isOpaque {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)acceptsFirstMouse:(NSEvent *)event {
|
|
||||||
(void)event;
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSView *)hitTest:(NSPoint)point {
|
|
||||||
(void)point;
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setFrame:(NSRect)frame {
|
|
||||||
[super setFrame:frame];
|
|
||||||
[self setNeedsDisplay:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)drawRect:(NSRect)dirtyRect {
|
|
||||||
(void)dirtyRect;
|
|
||||||
|
|
||||||
NSRect bounds = [self bounds];
|
|
||||||
CGFloat radius = MAX(0.0, self.cornerRadius);
|
|
||||||
NSBezierPath *borderPath = [NSBezierPath
|
|
||||||
bezierPathWithRoundedRect:NSInsetRect(bounds, 0.5, 0.5)
|
|
||||||
xRadius:radius
|
|
||||||
yRadius:radius];
|
|
||||||
|
|
||||||
if (@available(macOS 10.14, *)) {
|
|
||||||
NSAppearance *appearance = [self effectiveAppearance];
|
|
||||||
BOOL isDark =
|
|
||||||
[[appearance bestMatchFromAppearancesWithNames:@[
|
|
||||||
NSAppearanceNameAqua, NSAppearanceNameDarkAqua
|
|
||||||
]] isEqualToString:NSAppearanceNameDarkAqua];
|
|
||||||
|
|
||||||
if (isDark) {
|
|
||||||
[[NSColor colorWithWhite:1.0 alpha:0.18] setStroke];
|
|
||||||
} else {
|
|
||||||
[[NSColor colorWithWhite:1.0 alpha:0.72] setStroke];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[[NSColor colorWithWhite:1.0 alpha:0.72] setStroke];
|
|
||||||
}
|
|
||||||
|
|
||||||
borderPath.lineWidth = 1.0;
|
|
||||||
[borderPath stroke];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface ElectrobunNativeDragView : NSView
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation ElectrobunNativeDragView
|
|
||||||
- (BOOL)isOpaque {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)drawRect:(NSRect)dirtyRect {
|
|
||||||
(void)dirtyRect;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)mouseDown:(NSEvent *)event {
|
|
||||||
NSWindow *window = [self window];
|
|
||||||
if (window != nil && event != nil) {
|
|
||||||
[window performWindowDragWithEvent:event];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
static NSVisualEffectView *findVibrancyView(NSView *contentView) {
|
|
||||||
for (NSView *subview in [contentView subviews]) {
|
|
||||||
if ([subview isKindOfClass:[NSVisualEffectView class]] &&
|
|
||||||
[[subview identifier]
|
|
||||||
isEqualToString:kElectrobunVibrancyViewIdentifier]) {
|
|
||||||
return (NSVisualEffectView *)subview;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ElectrobunNativeDragView *findNativeDragView(NSView *contentView) {
|
|
||||||
for (NSView *subview in [contentView subviews]) {
|
|
||||||
if ([subview isKindOfClass:[ElectrobunNativeDragView class]] &&
|
|
||||||
[[subview identifier]
|
|
||||||
isEqualToString:kElectrobunNativeDragViewIdentifier]) {
|
|
||||||
return (ElectrobunNativeDragView *)subview;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ElectrobunWindowBorderView *findWindowBorderView(NSView *contentView) {
|
|
||||||
for (NSView *subview in [contentView subviews]) {
|
|
||||||
if ([subview isKindOfClass:[ElectrobunWindowBorderView class]] &&
|
|
||||||
[[subview identifier]
|
|
||||||
isEqualToString:kElectrobunWindowBorderViewIdentifier]) {
|
|
||||||
return (ElectrobunWindowBorderView *)subview;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void applyWindowCornerRadius(NSWindow *window, CGFloat radius) {
|
|
||||||
gWindowCornerRadius = MAX(0.0, radius);
|
|
||||||
|
|
||||||
NSView *contentView = [window contentView];
|
|
||||||
if (contentView == nil) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
contentView.wantsLayer = YES;
|
|
||||||
contentView.layer.cornerRadius = gWindowCornerRadius;
|
|
||||||
contentView.layer.masksToBounds = YES;
|
|
||||||
|
|
||||||
NSVisualEffectView *effectView = findVibrancyView(contentView);
|
|
||||||
if (effectView != nil) {
|
|
||||||
effectView.wantsLayer = YES;
|
|
||||||
effectView.layer.cornerRadius = gWindowCornerRadius;
|
|
||||||
effectView.layer.masksToBounds = YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
ElectrobunWindowBorderView *borderView = findWindowBorderView(contentView);
|
|
||||||
if (borderView != nil) {
|
|
||||||
borderView.cornerRadius = gWindowCornerRadius;
|
|
||||||
[borderView setNeedsDisplay:YES];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ensureWindowBorder(NSWindow *window) {
|
|
||||||
NSView *contentView = [window contentView];
|
|
||||||
if (contentView == nil) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ElectrobunWindowBorderView *borderView = findWindowBorderView(contentView);
|
|
||||||
if (borderView == nil) {
|
|
||||||
borderView = [[ElectrobunWindowBorderView alloc]
|
|
||||||
initWithFrame:[contentView bounds]];
|
|
||||||
[borderView setIdentifier:kElectrobunWindowBorderViewIdentifier];
|
|
||||||
[borderView
|
|
||||||
setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
|
|
||||||
}
|
|
||||||
|
|
||||||
borderView.cornerRadius = gWindowCornerRadius;
|
|
||||||
[borderView setFrame:[contentView bounds]];
|
|
||||||
|
|
||||||
if ([borderView superview] == nil) {
|
|
||||||
[contentView addSubview:borderView
|
|
||||||
positioned:NSWindowAbove
|
|
||||||
relativeTo:nil];
|
|
||||||
} else {
|
|
||||||
[borderView removeFromSuperview];
|
|
||||||
[contentView addSubview:borderView
|
|
||||||
positioned:NSWindowAbove
|
|
||||||
relativeTo:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
[borderView setNeedsDisplay:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" bool enableWindowVibrancy(void *windowPtr, double cornerRadius) {
|
|
||||||
if (windowPtr == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
__block BOOL success = NO;
|
|
||||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
||||||
NSWindow *window = (__bridge NSWindow *)windowPtr;
|
|
||||||
if (![window isKindOfClass:[NSWindow class]]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyWindowCornerRadius(window, (CGFloat)cornerRadius);
|
|
||||||
|
|
||||||
[window setOpaque:NO];
|
|
||||||
[window setBackgroundColor:[NSColor clearColor]];
|
|
||||||
[window setTitlebarAppearsTransparent:YES];
|
|
||||||
[window setHasShadow:YES];
|
|
||||||
|
|
||||||
NSView *contentView = [window contentView];
|
|
||||||
if (contentView == nil) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSVisualEffectView *effectView = findVibrancyView(contentView);
|
|
||||||
|
|
||||||
if (effectView == nil) {
|
|
||||||
effectView = [[NSVisualEffectView alloc]
|
|
||||||
initWithFrame:[contentView bounds]];
|
|
||||||
[effectView setIdentifier:kElectrobunVibrancyViewIdentifier];
|
|
||||||
[effectView
|
|
||||||
setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (@available(macOS 10.14, *)) {
|
|
||||||
[effectView setMaterial:NSVisualEffectMaterialUnderWindowBackground];
|
|
||||||
} else {
|
|
||||||
[effectView setMaterial:NSVisualEffectMaterialSidebar];
|
|
||||||
}
|
|
||||||
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
|
|
||||||
[effectView setState:NSVisualEffectStateActive];
|
|
||||||
|
|
||||||
if ([effectView superview] == nil) {
|
|
||||||
NSView *relativeView = [[contentView subviews] firstObject];
|
|
||||||
if (relativeView != nil) {
|
|
||||||
[contentView addSubview:effectView
|
|
||||||
positioned:NSWindowBelow
|
|
||||||
relativeTo:relativeView];
|
|
||||||
} else {
|
|
||||||
[contentView addSubview:effectView];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureWindowBorder(window);
|
|
||||||
applyWindowCornerRadius(window, gWindowCornerRadius);
|
|
||||||
|
|
||||||
[window invalidateShadow];
|
|
||||||
success = YES;
|
|
||||||
});
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" bool setWindowCornerRadius(void *windowPtr, double cornerRadius) {
|
|
||||||
if (windowPtr == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
__block BOOL success = NO;
|
|
||||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
||||||
NSWindow *window = (__bridge NSWindow *)windowPtr;
|
|
||||||
if (![window isKindOfClass:[NSWindow class]]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyWindowCornerRadius(window, (CGFloat)cornerRadius);
|
|
||||||
ensureWindowBorder(window);
|
|
||||||
[window invalidateShadow];
|
|
||||||
success = YES;
|
|
||||||
});
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" bool ensureWindowShadow(void *windowPtr) {
|
|
||||||
if (windowPtr == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
__block BOOL success = NO;
|
|
||||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
||||||
NSWindow *window = (__bridge NSWindow *)windowPtr;
|
|
||||||
if (![window isKindOfClass:[NSWindow class]]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
[window setHasShadow:YES];
|
|
||||||
[window invalidateShadow];
|
|
||||||
ensureWindowBorder(window);
|
|
||||||
applyWindowCornerRadius(window, gWindowCornerRadius);
|
|
||||||
success = YES;
|
|
||||||
});
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" bool setWindowTrafficLightsPosition(void *windowPtr, double x,
|
|
||||||
double yFromTop) {
|
|
||||||
if (windowPtr == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
__block BOOL success = NO;
|
|
||||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
||||||
NSWindow *window = (__bridge NSWindow *)windowPtr;
|
|
||||||
if (![window isKindOfClass:[NSWindow class]]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSButton *closeButton =
|
|
||||||
[window standardWindowButton:NSWindowCloseButton];
|
|
||||||
NSButton *minimizeButton =
|
|
||||||
[window standardWindowButton:NSWindowMiniaturizeButton];
|
|
||||||
NSButton *zoomButton = [window standardWindowButton:NSWindowZoomButton];
|
|
||||||
|
|
||||||
if (closeButton == nil || minimizeButton == nil || zoomButton == nil) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSView *buttonContainer = [closeButton superview];
|
|
||||||
if (buttonContainer == nil) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGFloat spacing = NSMinX(minimizeButton.frame) - NSMinX(closeButton.frame);
|
|
||||||
if (spacing <= 0) {
|
|
||||||
spacing = closeButton.frame.size.width + 6.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL flipped = [buttonContainer isFlipped];
|
|
||||||
CGFloat targetY = yFromTop;
|
|
||||||
if (!flipped) {
|
|
||||||
targetY = buttonContainer.frame.size.height - yFromTop -
|
|
||||||
closeButton.frame.size.height;
|
|
||||||
}
|
|
||||||
targetY = MAX(0.0, targetY);
|
|
||||||
|
|
||||||
CGFloat currentX = x;
|
|
||||||
NSArray *buttons = @[ closeButton, minimizeButton, zoomButton ];
|
|
||||||
for (NSButton *button in buttons) {
|
|
||||||
[button setFrameOrigin:NSMakePoint(currentX, targetY)];
|
|
||||||
currentX += spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
[buttonContainer setNeedsLayout:YES];
|
|
||||||
[buttonContainer layoutSubtreeIfNeeded];
|
|
||||||
[window invalidateShadow];
|
|
||||||
success = YES;
|
|
||||||
});
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" bool setNativeWindowDragRegion(void *windowPtr, double x,
|
|
||||||
double height) {
|
|
||||||
if (windowPtr == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
__block BOOL success = NO;
|
|
||||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
||||||
NSWindow *window = (__bridge NSWindow *)windowPtr;
|
|
||||||
if (![window isKindOfClass:[NSWindow class]]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSView *contentView = [window contentView];
|
|
||||||
if (contentView == nil) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGFloat dragX = MAX(0.0, x);
|
|
||||||
CGFloat dragHeight = MAX(0.0, height);
|
|
||||||
CGFloat dragWidth = MAX(0.0, contentView.bounds.size.width - dragX);
|
|
||||||
if (dragHeight <= 0.0 || dragWidth <= 0.0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL flipped = [contentView isFlipped];
|
|
||||||
CGFloat dragY = flipped ? 0.0 : contentView.bounds.size.height - dragHeight;
|
|
||||||
dragY = MAX(0.0, dragY);
|
|
||||||
|
|
||||||
ElectrobunNativeDragView *dragView = findNativeDragView(contentView);
|
|
||||||
if (dragView == nil) {
|
|
||||||
dragView = [[ElectrobunNativeDragView alloc] initWithFrame:NSZeroRect];
|
|
||||||
[dragView setIdentifier:kElectrobunNativeDragViewIdentifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
[dragView setFrame:NSMakeRect(dragX, dragY, dragWidth, dragHeight)];
|
|
||||||
[dragView setAutoresizingMask:NSViewWidthSizable];
|
|
||||||
|
|
||||||
if ([dragView superview] == nil) {
|
|
||||||
[contentView addSubview:dragView
|
|
||||||
positioned:NSWindowAbove
|
|
||||||
relativeTo:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
success = YES;
|
|
||||||
});
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
@ -94,8 +94,7 @@
|
|||||||
"build:electron": "vite build && electron-builder",
|
"build:electron": "vite build && electron-builder",
|
||||||
"build:cloudflare": "cross-env VITE_DEPLOY_TARGET=cloudflare vite build",
|
"build:cloudflare": "cross-env VITE_DEPLOY_TARGET=cloudflare vite build",
|
||||||
"deploy": "npm run build:cloudflare && wrangler pages deploy --branch main",
|
"deploy": "npm run build:cloudflare && wrangler pages deploy --branch main",
|
||||||
"postinstall": "bun scripts/patch-electrobun-src.mjs && bun scripts/build-macos-effects.mjs",
|
"postinstall": "bun scripts/patch-electrobun-src.mjs",
|
||||||
"build:macos-effects": "bun scripts/build-macos-effects.mjs",
|
|
||||||
"clean": "bun scripts/clean-build.mjs"
|
"clean": "bun scripts/clean-build.mjs"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
|||||||
@ -43,7 +43,6 @@ Function .onInit
|
|||||||
${If} ${Silent}
|
${If} ${Silent}
|
||||||
SetAutoClose true
|
SetAutoClose true
|
||||||
${EndIf}
|
${EndIf}
|
||||||
!insertmacro uninstallPreviousFarmControl
|
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
|
||||||
Section "Farm Control" SecMain
|
Section "Farm Control" SecMain
|
||||||
|
|||||||
@ -1,33 +1,3 @@
|
|||||||
!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
|
!macro createDesktopShortcut
|
||||||
CreateShortCut "$DESKTOP\Farm Control.lnk" "$INSTDIR\bin\launcher.exe"
|
CreateShortCut "$DESKTOP\Farm Control.lnk" "$INSTDIR\bin\launcher.exe"
|
||||||
!macroend
|
!macroend
|
||||||
|
|||||||
@ -23,42 +23,8 @@
|
|||||||
<Property Id="DISABLEADVTSHORTCUTS" Value="1" />
|
<Property Id="DISABLEADVTSHORTCUTS" Value="1" />
|
||||||
<Property Id="MSIINSTALLPERUSER" Value="1" />
|
<Property Id="MSIINSTALLPERUSER" Value="1" />
|
||||||
|
|
||||||
<Property Id="PREVIOUS_UNINSTALL_CMD">
|
|
||||||
<RegistrySearch
|
|
||||||
Id="PreviousUninstallCmdSearch"
|
|
||||||
Root="HKLM"
|
|
||||||
Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control"
|
|
||||||
Name="UninstallString"
|
|
||||||
Type="raw" />
|
|
||||||
</Property>
|
|
||||||
|
|
||||||
<Property Id="PREVIOUS_INSTALL_DIR">
|
|
||||||
<RegistrySearch
|
|
||||||
Id="PreviousInstallDirSearch"
|
|
||||||
Root="HKLM"
|
|
||||||
Key="Software\Tom Butcher\Farm Control"
|
|
||||||
Name="InstallDir"
|
|
||||||
Type="directory" />
|
|
||||||
</Property>
|
|
||||||
|
|
||||||
<Binary Id="WrappedExe" SourceFile="__SETUP_EXE__" />
|
<Binary Id="WrappedExe" SourceFile="__SETUP_EXE__" />
|
||||||
|
|
||||||
<CustomAction
|
|
||||||
Id="KillFarmControl"
|
|
||||||
BinaryKey="WixCA"
|
|
||||||
DllEntry="WixQuietExec"
|
|
||||||
Execute="deferred"
|
|
||||||
Impersonate="no"
|
|
||||||
Return="ignore" />
|
|
||||||
|
|
||||||
<CustomAction
|
|
||||||
Id="UninstallPrevious"
|
|
||||||
BinaryKey="WixCA"
|
|
||||||
DllEntry="WixQuietExec"
|
|
||||||
Execute="deferred"
|
|
||||||
Impersonate="no"
|
|
||||||
Return="ignore" />
|
|
||||||
|
|
||||||
<CustomAction
|
<CustomAction
|
||||||
Id="RunInstaller"
|
Id="RunInstaller"
|
||||||
BinaryKey="WrappedExe"
|
BinaryKey="WrappedExe"
|
||||||
@ -79,33 +45,8 @@
|
|||||||
<ComponentRef Id="EmptyComponent" />
|
<ComponentRef Id="EmptyComponent" />
|
||||||
</Feature>
|
</Feature>
|
||||||
|
|
||||||
<CustomAction
|
|
||||||
Id="SetKillFarmControl"
|
|
||||||
Property="QtKillFarmControl"
|
|
||||||
Value="taskkill.exe /F /IM launcher.exe /T"
|
|
||||||
Execute="immediate" />
|
|
||||||
|
|
||||||
<CustomAction
|
|
||||||
Id="SetUninstallPreviousFromRegistry"
|
|
||||||
Property="QtUninstallPrevious"
|
|
||||||
Value=""[PREVIOUS_UNINSTALL_CMD]" /S"
|
|
||||||
Execute="immediate" />
|
|
||||||
|
|
||||||
<CustomAction
|
|
||||||
Id="SetUninstallPreviousFromInstallDir"
|
|
||||||
Property="QtUninstallPrevious"
|
|
||||||
Value=""[PREVIOUS_INSTALL_DIR]Uninstall.exe" /S"
|
|
||||||
Execute="immediate" />
|
|
||||||
|
|
||||||
<InstallExecuteSequence>
|
<InstallExecuteSequence>
|
||||||
<Custom Action="SetKillFarmControl" Before="KillFarmControl">NOT Installed</Custom>
|
<Custom Action="RunInstaller" After="ProcessComponents">NOT Installed</Custom>
|
||||||
<Custom Action="KillFarmControl" After="SetKillFarmControl">NOT Installed</Custom>
|
|
||||||
<Custom Action="SetUninstallPreviousFromRegistry" After="KillFarmControl">NOT Installed AND PREVIOUS_UNINSTALL_CMD</Custom>
|
|
||||||
<Custom Action="SetUninstallPreviousFromInstallDir" After="KillFarmControl">NOT Installed AND NOT PREVIOUS_UNINSTALL_CMD AND PREVIOUS_INSTALL_DIR</Custom>
|
|
||||||
<Custom Action="UninstallPrevious" After="SetUninstallPreviousFromRegistry">NOT Installed AND (PREVIOUS_UNINSTALL_CMD OR PREVIOUS_INSTALL_DIR)</Custom>
|
|
||||||
<Custom Action="UninstallPrevious" After="SetUninstallPreviousFromInstallDir">NOT Installed AND (PREVIOUS_UNINSTALL_CMD OR PREVIOUS_INSTALL_DIR)</Custom>
|
|
||||||
<Custom Action="RunInstaller" After="UninstallPrevious">NOT Installed AND (PREVIOUS_UNINSTALL_CMD OR PREVIOUS_INSTALL_DIR)</Custom>
|
|
||||||
<Custom Action="RunInstaller" After="KillFarmControl">NOT Installed AND NOT PREVIOUS_UNINSTALL_CMD AND NOT PREVIOUS_INSTALL_DIR</Custom>
|
|
||||||
</InstallExecuteSequence>
|
</InstallExecuteSequence>
|
||||||
</Product>
|
</Product>
|
||||||
</Wix>
|
</Wix>
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import { spawnSync } from "node:child_process";
|
|
||||||
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");
|
|
||||||
|
|
||||||
function createPlaceholder() {
|
|
||||||
mkdirSync(path.dirname(outFile), { recursive: true });
|
|
||||||
writeFileSync(outFile, "");
|
|
||||||
console.log(`build-macos-effects: created placeholder dylib at ${outFile}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform !== "darwin") {
|
|
||||||
createPlaceholder();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existsSync(srcFile)) {
|
|
||||||
console.error(`build-macos-effects: missing source file ${srcFile}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdirSync(path.dirname(outFile), { recursive: true });
|
|
||||||
|
|
||||||
const result = spawnSync(
|
|
||||||
"xcrun",
|
|
||||||
[
|
|
||||||
"clang++",
|
|
||||||
"-dynamiclib",
|
|
||||||
"-fobjc-arc",
|
|
||||||
"-framework",
|
|
||||||
"Cocoa",
|
|
||||||
srcFile,
|
|
||||||
"-o",
|
|
||||||
outFile,
|
|
||||||
],
|
|
||||||
{ cwd: rootDir, stdio: "inherit" },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.status !== 0) {
|
|
||||||
process.exit(result.status ?? 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { size } = statSync(outFile);
|
|
||||||
console.log(`build-macos-effects: built ${outFile} (${size} bytes)`);
|
|
||||||
@ -103,12 +103,12 @@ $wixObj = Join-Path $workDir "project.wixobj"
|
|||||||
|
|
||||||
Push-Location $workDir
|
Push-Location $workDir
|
||||||
try {
|
try {
|
||||||
& $wix.Candle "-arch" "x64" "-ext" "WixUtilExtension" $projectWxs
|
& $wix.Candle "-arch" "x64" $projectWxs
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
throw "candle.exe failed with exit code $LASTEXITCODE"
|
throw "candle.exe failed with exit code $LASTEXITCODE"
|
||||||
}
|
}
|
||||||
|
|
||||||
& $wix.Light "-out" $outputMsiPath "-spdb" "-sw1076" "-ext" "WixUtilExtension" $wixObj
|
& $wix.Light "-out" $outputMsiPath "-spdb" "-sw1076" $wixObj
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
throw "light.exe failed with exit code $LASTEXITCODE"
|
throw "light.exe failed with exit code $LASTEXITCODE"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,34 +3,34 @@ import {
|
|||||||
existsSync,
|
existsSync,
|
||||||
mkdirSync,
|
mkdirSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
writeFileSync
|
writeFileSync,
|
||||||
} from 'node:fs'
|
} from "node:fs";
|
||||||
import path from 'node:path'
|
import path from "node:path";
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
const electrobunDir = path.join(rootDir, 'node_modules/electrobun')
|
const electrobunDir = path.join(rootDir, "node_modules/electrobun");
|
||||||
const sharedSrcDir = path.join(electrobunDir, 'src/shared')
|
const sharedSrcDir = path.join(electrobunDir, "src/shared");
|
||||||
const sharedDistDir = path.join(electrobunDir, 'dist/api/shared')
|
const sharedDistDir = path.join(electrobunDir, "dist/api/shared");
|
||||||
|
|
||||||
if (!existsSync(sharedDistDir)) {
|
if (!existsSync(sharedDistDir)) {
|
||||||
console.error('patch-electrobun-src: electrobun dist/api/shared not found')
|
console.error("patch-electrobun-src: electrobun dist/api/shared not found");
|
||||||
process.exit(1)
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
mkdirSync(sharedSrcDir, { recursive: true })
|
mkdirSync(sharedSrcDir, { recursive: true });
|
||||||
|
|
||||||
for (const fileName of [
|
for (const fileName of [
|
||||||
'cef-version.ts',
|
"cef-version.ts",
|
||||||
'bun-version.ts',
|
"bun-version.ts",
|
||||||
'electrobun-version.ts',
|
"electrobun-version.ts",
|
||||||
'naming.ts',
|
"naming.ts",
|
||||||
'rpc.ts'
|
"rpc.ts",
|
||||||
]) {
|
]) {
|
||||||
const source = path.join(sharedDistDir, fileName)
|
const source = path.join(sharedDistDir, fileName);
|
||||||
const destination = path.join(sharedSrcDir, fileName)
|
const destination = path.join(sharedSrcDir, fileName);
|
||||||
if (existsSync(source)) {
|
if (existsSync(source)) {
|
||||||
cpSync(source, destination)
|
cpSync(source, destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,14 +109,14 @@ export function getPlatformOS(): SupportedOS {
|
|||||||
export function getPlatformArch(): SupportedArch {
|
export function getPlatformArch(): SupportedArch {
|
||||||
return ARCH;
|
return ARCH;
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
|
|
||||||
writeFileSync(path.join(sharedSrcDir, 'platform.ts'), platformPatch)
|
writeFileSync(path.join(sharedSrcDir, "platform.ts"), platformPatch);
|
||||||
|
|
||||||
const templatesDir = path.join(electrobunDir, 'src/cli/templates')
|
const templatesDir = path.join(electrobunDir, "src/cli/templates");
|
||||||
mkdirSync(templatesDir, { recursive: true })
|
mkdirSync(templatesDir, { recursive: true });
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
path.join(templatesDir, 'embedded.ts'),
|
path.join(templatesDir, "embedded.ts"),
|
||||||
`export function getTemplateNames() {
|
`export function getTemplateNames() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -124,8 +124,8 @@ writeFileSync(
|
|||||||
export function getTemplate(name: string) {
|
export function getTemplate(name: string) {
|
||||||
throw new Error(\`Template not available in patched electrobun CLI: \${name}\`);
|
throw new Error(\`Template not available in patched electrobun CLI: \${name}\`);
|
||||||
}
|
}
|
||||||
`
|
`,
|
||||||
)
|
);
|
||||||
|
|
||||||
const RCEDIT_RESOLVER_FN = `function resolveProjectRceditPkgPath(projectRoot) {
|
const RCEDIT_RESOLVER_FN = `function resolveProjectRceditPkgPath(projectRoot) {
|
||||||
const candidates = [
|
const candidates = [
|
||||||
@ -140,41 +140,35 @@ const RCEDIT_RESOLVER_FN = `function resolveProjectRceditPkgPath(projectRoot) {
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
"rcedit not found under " + projectRoot + ". Install rcedit in the project (bun add -d rcedit).",
|
"rcedit not found under " + projectRoot + ". Install rcedit in the project (bun add -d rcedit).",
|
||||||
);
|
);
|
||||||
}`
|
}`;
|
||||||
|
|
||||||
const cliPath = path.join(electrobunDir, 'src/cli/index.ts')
|
const cliPath = path.join(electrobunDir, "src/cli/index.ts");
|
||||||
let cliSource = readFileSync(cliPath, 'utf8')
|
let cliSource = readFileSync(cliPath, "utf8");
|
||||||
if (!cliSource.includes('HOST_ARCH')) {
|
if (!cliSource.includes("HOST_ARCH")) {
|
||||||
cliSource = cliSource.replace(
|
cliSource = cliSource.replace(
|
||||||
'import { OS, ARCH } from "../shared/platform";',
|
'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(
|
cliSource = cliSource.replace(
|
||||||
'const hostPaths = getPlatformPaths(OS, ARCH);',
|
"const hostPaths = getPlatformPaths(OS, ARCH);",
|
||||||
'const hostPaths = getPlatformPaths(OS, HOST_ARCH);'
|
"const hostPaths = getPlatformPaths(OS, HOST_ARCH);",
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
if (!cliSource.includes('toolPaths')) {
|
if (!cliSource.includes("toolPaths")) {
|
||||||
cliSource = cliSource.replace(
|
cliSource = cliSource.replace(
|
||||||
'const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);',
|
"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);'
|
"const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);\n\t\tconst toolPaths = getPlatformPaths(currentTarget.os, ARCH !== HOST_ARCH ? HOST_ARCH : ARCH);",
|
||||||
)
|
);
|
||||||
cliSource = cliSource.replaceAll(
|
cliSource = cliSource.replaceAll("const zstdPath = targetPaths.ZSTD;", "const zstdPath = toolPaths.ZSTD;");
|
||||||
'const zstdPath = targetPaths.ZSTD;',
|
cliSource = cliSource.replaceAll("const bsdiffpath = targetPaths.BSDIFF;", "const bsdiffpath = toolPaths.BSDIFF;");
|
||||||
'const zstdPath = toolPaths.ZSTD;'
|
|
||||||
)
|
|
||||||
cliSource = cliSource.replaceAll(
|
|
||||||
'const bsdiffpath = targetPaths.BSDIFF;',
|
|
||||||
'const bsdiffpath = toolPaths.BSDIFF;'
|
|
||||||
)
|
|
||||||
cliSource = cliSource.replace(
|
cliSource = cliSource.replace(
|
||||||
'zigAsarCli = join(targetPaths.BSPATCH).replace("bspatch", "zig-asar");',
|
"zigAsarCli = join(targetPaths.BSPATCH).replace(\"bspatch\", \"zig-asar\");",
|
||||||
'zigAsarCli = join(toolPaths.BSPATCH).replace("bspatch", "zig-asar");'
|
"zigAsarCli = join(toolPaths.BSPATCH).replace(\"bspatch\", \"zig-asar\");",
|
||||||
)
|
);
|
||||||
writeFileSync(cliPath, cliSource)
|
writeFileSync(cliPath, cliSource);
|
||||||
cliSource = readFileSync(cliPath, 'utf8')
|
cliSource = readFileSync(cliPath, "utf8");
|
||||||
}
|
}
|
||||||
if (!cliSource.includes('hookBunBinary')) {
|
if (!cliSource.includes("hookBunBinary")) {
|
||||||
cliSource = cliSource.replace(
|
cliSource = cliSource.replace(
|
||||||
`const hostPaths = getPlatformPaths(OS, HOST_ARCH);
|
`const hostPaths = getPlatformPaths(OS, HOST_ARCH);
|
||||||
|
|
||||||
@ -184,41 +178,41 @@ if (!cliSource.includes('hookBunBinary')) {
|
|||||||
? hostPaths.BUN_BINARY
|
? hostPaths.BUN_BINARY
|
||||||
: process.execPath;
|
: process.execPath;
|
||||||
|
|
||||||
const result = Bun.spawnSync([hookBunBinary, hookScript],`
|
const result = Bun.spawnSync([hookBunBinary, hookScript],`,
|
||||||
)
|
);
|
||||||
cliSource = cliSource.replace(
|
cliSource = cliSource.replace(
|
||||||
'console.error("Tried to run with bun at:", hostPaths.BUN_BINARY);',
|
"console.error(\"Tried to run with bun at:\", hostPaths.BUN_BINARY);",
|
||||||
'console.error("Tried to run with bun at:", hookBunBinary);'
|
"console.error(\"Tried to run with bun at:\", hookBunBinary);",
|
||||||
)
|
);
|
||||||
writeFileSync(cliPath, cliSource)
|
writeFileSync(cliPath, cliSource);
|
||||||
cliSource = readFileSync(cliPath, 'utf8')
|
cliSource = readFileSync(cliPath, "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cliSource.includes('resolveProjectRceditPkgPath')) {
|
if (!cliSource.includes("resolveProjectRceditPkgPath")) {
|
||||||
cliSource = cliSource.replaceAll(
|
cliSource = cliSource.replaceAll(
|
||||||
'const rceditPkgPath = require.resolve("rcedit/package.json");',
|
"const rceditPkgPath = require.resolve(\"rcedit/package.json\");",
|
||||||
'const rceditPkgPath = resolveProjectRceditPkgPath(projectRoot);'
|
"const rceditPkgPath = resolveProjectRceditPkgPath(projectRoot);",
|
||||||
)
|
);
|
||||||
cliSource = cliSource.replace(
|
cliSource = cliSource.replace(
|
||||||
'function getPlatformPaths(',
|
"function getPlatformPaths(",
|
||||||
`${RCEDIT_RESOLVER_FN}
|
`${RCEDIT_RESOLVER_FN}
|
||||||
|
|
||||||
function getPlatformPaths(`
|
function getPlatformPaths(`,
|
||||||
)
|
);
|
||||||
writeFileSync(cliPath, cliSource)
|
writeFileSync(cliPath, cliSource);
|
||||||
cliSource = readFileSync(cliPath, 'utf8')
|
cliSource = readFileSync(cliPath, "utf8");
|
||||||
} else {
|
} else {
|
||||||
cliSource = cliSource.replace(
|
cliSource = cliSource.replace(
|
||||||
/function resolveProjectRceditPkgPath\(projectRoot\) \{[\s\S]*?\n\}/m,
|
/function resolveProjectRceditPkgPath\(projectRoot\) \{[\s\S]*?\n\}/m,
|
||||||
RCEDIT_RESOLVER_FN
|
RCEDIT_RESOLVER_FN,
|
||||||
)
|
);
|
||||||
writeFileSync(cliPath, cliSource)
|
writeFileSync(cliPath, cliSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
const electrobunCjsPath = path.join(electrobunDir, 'bin/electrobun.cjs')
|
const electrobunCjsPath = path.join(electrobunDir, "bin/electrobun.cjs");
|
||||||
let electrobunCjs = readFileSync(electrobunCjsPath, 'utf8')
|
let electrobunCjs = readFileSync(electrobunCjsPath, "utf8");
|
||||||
|
|
||||||
if (!electrobunCjs.includes('farmcontrol-use-patched-cli')) {
|
if (!electrobunCjs.includes("farmcontrol-use-patched-cli")) {
|
||||||
electrobunCjs = electrobunCjs.replace(
|
electrobunCjs = electrobunCjs.replace(
|
||||||
`async function main() {
|
`async function main() {
|
||||||
try {
|
try {
|
||||||
@ -265,21 +259,19 @@ if (!electrobunCjs.includes('farmcontrol-use-patched-cli')) {
|
|||||||
const child = spawn(cliPath, args, {
|
const child = spawn(cliPath, args, {
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
cwd: process.cwd()
|
cwd: process.cwd()
|
||||||
});`
|
});`,
|
||||||
)
|
);
|
||||||
writeFileSync(electrobunCjsPath, electrobunCjs)
|
writeFileSync(electrobunCjsPath, electrobunCjs);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rceditSrc = path.join(rootDir, 'node_modules/rcedit')
|
const rceditSrc = path.join(rootDir, "node_modules/rcedit");
|
||||||
const rceditDest = path.join(electrobunDir, 'node_modules/rcedit')
|
const rceditDest = path.join(electrobunDir, "node_modules/rcedit");
|
||||||
if (existsSync(rceditSrc)) {
|
if (existsSync(rceditSrc)) {
|
||||||
mkdirSync(path.join(electrobunDir, 'node_modules'), { recursive: true })
|
mkdirSync(path.join(electrobunDir, "node_modules"), { recursive: true });
|
||||||
cpSync(rceditSrc, rceditDest, { recursive: true, force: true })
|
cpSync(rceditSrc, rceditDest, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
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`);
|
||||||
|
|
||||||
console.log(
|
console.log("patch-electrobun-src: electrobun src/shared ready for cross-arch builds");
|
||||||
'patch-electrobun-src: electrobun src/shared ready for cross-arch builds'
|
|
||||||
)
|
|
||||||
|
|||||||
@ -36,16 +36,6 @@ if (buildEnv === "stable" && process.env.ELECTROBUN_OS === "macos") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildMacosEffects = spawnSync(
|
|
||||||
"bun",
|
|
||||||
[path.join(rootDir, "scripts/build-macos-effects.mjs")],
|
|
||||||
{ cwd: rootDir, stdio: "inherit", env: process.env },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (buildMacosEffects.status !== 0) {
|
|
||||||
process.exit(buildMacosEffects.status ?? 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const prepareIcons = spawnSync(
|
const prepareIcons = spawnSync(
|
||||||
"bun",
|
"bun",
|
||||||
[
|
[
|
||||||
|
|||||||
@ -26,16 +26,6 @@ if (patchResult.status !== 0) {
|
|||||||
process.exit(patchResult.status ?? 1);
|
process.exit(patchResult.status ?? 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const macosEffectsResult = spawnSync(
|
|
||||||
"bun",
|
|
||||||
[path.join(rootDir, "scripts/build-macos-effects.mjs")],
|
|
||||||
{ cwd: rootDir, stdio: "inherit", env: process.env },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (macosEffectsResult.status !== 0) {
|
|
||||||
process.exit(macosEffectsResult.status ?? 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ensureCoreEnv = {
|
const ensureCoreEnv = {
|
||||||
...process.env,
|
...process.env,
|
||||||
ELECTROBUN_TARGET_ARCH: targetArch,
|
ELECTROBUN_TARGET_ARCH: targetArch,
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const DashboardLayout = ({ children }) => {
|
|||||||
<MessageProvider>
|
<MessageProvider>
|
||||||
<Layout
|
<Layout
|
||||||
style={{ height: 'var(--unit-100vh)' }}
|
style={{ height: 'var(--unit-100vh)' }}
|
||||||
className={`${isDarkMode ? 'dark-mode' : 'light-mode'} main-layout`}
|
className={isDarkMode ? 'dark-mode' : 'light-mode'}
|
||||||
>
|
>
|
||||||
<DashboardNavigation />
|
<DashboardNavigation />
|
||||||
<Layout>
|
<Layout>
|
||||||
|
|||||||
@ -199,138 +199,134 @@ const DashboardNavigation = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<div className='electrobun-webkit-app-region-no-drag'>
|
<Menu
|
||||||
<Menu
|
mode='horizontal'
|
||||||
mode='horizontal'
|
className={isElectron ? 'electron-navigation' : null}
|
||||||
className={isElectron ? 'electron-navigation' : null}
|
items={mainMenuItems}
|
||||||
items={mainMenuItems}
|
style={{
|
||||||
style={{
|
flexWrap: 'wrap',
|
||||||
flexWrap: 'wrap',
|
flexGrow: isMobile ? 0 : 1,
|
||||||
flexGrow: isMobile ? 0 : 1,
|
border: 0,
|
||||||
border: 0,
|
width: isMobile ? '64px' : 'unset'
|
||||||
width: isMobile ? '64px' : 'unset'
|
}}
|
||||||
}}
|
onClick={handleMainMenuClick}
|
||||||
onClick={handleMainMenuClick}
|
selectedKeys={[selectedKey]}
|
||||||
selectedKeys={[selectedKey]}
|
overflowedIndicator={
|
||||||
overflowedIndicator={
|
<Button
|
||||||
<Button
|
type='text'
|
||||||
type='text'
|
icon={<MenuIcon />}
|
||||||
icon={<MenuIcon />}
|
style={{ marginBottom: '4px' }}
|
||||||
style={{ marginBottom: '4px' }}
|
/>
|
||||||
/>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{isMobile && <div style={{ flexGrow: 1 }} />}
|
{isMobile && <div style={{ flexGrow: 1 }} />}
|
||||||
<div className='electrobun-webkit-app-region-no-drag'>
|
<Flex
|
||||||
<Flex
|
gap={'small'}
|
||||||
gap={'small'}
|
align='center'
|
||||||
align='center'
|
style={{ marginTop: '-2px', marginRight: '6px' }}
|
||||||
style={{ marginTop: '-2px', marginRight: '6px' }}
|
>
|
||||||
>
|
<Space style={{ paddingTop: '2px', marginRight: '8px' }}>
|
||||||
<Space style={{ paddingTop: '2px', marginRight: '8px' }}>
|
<WebAppSwitcher />
|
||||||
<WebAppSwitcher />
|
<KeyboardShortcut
|
||||||
|
shortcut='alt+q'
|
||||||
|
hint='ALT Q'
|
||||||
|
onTrigger={() => showSpotlight()}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<SearchIcon />}
|
||||||
|
type='text'
|
||||||
|
style={{ marginTop: '4px' }}
|
||||||
|
onClick={() => showSpotlight()}
|
||||||
|
/>
|
||||||
|
</KeyboardShortcut>
|
||||||
|
<Badge
|
||||||
|
count={unreadCount}
|
||||||
|
size='small'
|
||||||
|
offset={[-5, 8]}
|
||||||
|
style={{ padding: 0, fontWeight: 600 }}
|
||||||
|
>
|
||||||
<KeyboardShortcut
|
<KeyboardShortcut
|
||||||
shortcut='alt+q'
|
shortcut='alt+n'
|
||||||
hint='ALT Q'
|
hint='ALT N'
|
||||||
onTrigger={() => showSpotlight()}
|
onTrigger={() => toggleNotificationCenter()}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
icon={<SearchIcon />}
|
icon={<BellIcon />}
|
||||||
type='text'
|
type='text'
|
||||||
style={{ marginTop: '4px' }}
|
style={{ marginTop: '2px' }}
|
||||||
onClick={() => showSpotlight()}
|
onClick={() => toggleNotificationCenter()}
|
||||||
/>
|
/>
|
||||||
</KeyboardShortcut>
|
</KeyboardShortcut>
|
||||||
<Badge
|
</Badge>
|
||||||
count={unreadCount}
|
</Space>
|
||||||
size='small'
|
{import.meta.env.MODE === 'development' && (
|
||||||
offset={[-5, 8]}
|
<Space>
|
||||||
style={{ padding: 0, fontWeight: 600 }}
|
{apiServerState === 'connected' ? (
|
||||||
>
|
<Tooltip title='Connected to api server' arrow={false}>
|
||||||
<KeyboardShortcut
|
|
||||||
shortcut='alt+n'
|
|
||||||
hint='ALT N'
|
|
||||||
onTrigger={() => toggleNotificationCenter()}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
icon={<BellIcon />}
|
|
||||||
type='text'
|
|
||||||
style={{ marginTop: '2px' }}
|
|
||||||
onClick={() => toggleNotificationCenter()}
|
|
||||||
/>
|
|
||||||
</KeyboardShortcut>
|
|
||||||
</Badge>
|
|
||||||
</Space>
|
|
||||||
{import.meta.env.MODE === 'development' && (
|
|
||||||
<Space>
|
|
||||||
{apiServerState === 'connected' ? (
|
|
||||||
<Tooltip title='Connected to api server' arrow={false}>
|
|
||||||
<Tag
|
|
||||||
color='success'
|
|
||||||
style={{ marginRight: 0 }}
|
|
||||||
icon={<CloudIcon />}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
{apiServerState === 'connecting' ? (
|
|
||||||
<Tooltip title='Connecting to api erver...' arrow={false}>
|
|
||||||
<Tag
|
|
||||||
color='warning'
|
|
||||||
style={{ marginRight: 0 }}
|
|
||||||
icon={<LoadingOutlined />}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
{apiServerState === 'disconnected' ? (
|
|
||||||
<Tooltip title='Disconnected from api server' arrow={false}>
|
|
||||||
<Tag
|
|
||||||
color='error'
|
|
||||||
style={{ marginRight: 0 }}
|
|
||||||
icon={<CloudIcon />}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
<Tooltip title='Developer' arrow={false}>
|
|
||||||
<Tag
|
<Tag
|
||||||
color='yellow'
|
color='success'
|
||||||
style={{ marginRight: 0 }}
|
style={{ marginRight: 0 }}
|
||||||
icon={<DeveloperIcon />}
|
icon={<CloudIcon />}
|
||||||
onClick={() => {
|
|
||||||
navigate('/dashboard/developer/sessionstorage')
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Space>
|
) : null}
|
||||||
)}
|
{apiServerState === 'connecting' ? (
|
||||||
{userProfile ? (
|
<Tooltip title='Connecting to api erver...' arrow={false}>
|
||||||
<Space>
|
<Tag
|
||||||
<Popover
|
color='warning'
|
||||||
content={userPopoverContent}
|
style={{ marginRight: 0 }}
|
||||||
placement='bottomRight'
|
icon={<LoadingOutlined />}
|
||||||
trigger='hover'
|
/>
|
||||||
open={userPopoverOpen}
|
</Tooltip>
|
||||||
onOpenChange={setUserPopoverOpen}
|
) : null}
|
||||||
arrow={false}
|
{apiServerState === 'disconnected' ? (
|
||||||
>
|
<Tooltip title='Disconnected from api server' arrow={false}>
|
||||||
<Tag style={{ marginRight: 0 }} icon={<PersonIcon />}>
|
<Tag
|
||||||
{!isMobile && (userProfile?.name || userProfile.username)}
|
color='error'
|
||||||
</Tag>
|
style={{ marginRight: 0 }}
|
||||||
</Popover>
|
icon={<CloudIcon />}
|
||||||
</Space>
|
/>
|
||||||
) : null}
|
</Tooltip>
|
||||||
{isElectron && availableUpdate ? (
|
) : null}
|
||||||
<Tag
|
<Tooltip title='Developer' arrow={false}>
|
||||||
icon={<CloudIcon />}
|
<Tag
|
||||||
style={{ cursor: 'pointer', margin: '2px 0 0 0' }}
|
color='yellow'
|
||||||
color='cyan'
|
style={{ marginRight: 0 }}
|
||||||
onClick={() => checkForUpdates()}
|
icon={<DeveloperIcon />}
|
||||||
|
onClick={() => {
|
||||||
|
navigate('/dashboard/developer/sessionstorage')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
{userProfile ? (
|
||||||
|
<Space>
|
||||||
|
<Popover
|
||||||
|
content={userPopoverContent}
|
||||||
|
placement='bottomRight'
|
||||||
|
trigger='hover'
|
||||||
|
open={userPopoverOpen}
|
||||||
|
onOpenChange={setUserPopoverOpen}
|
||||||
|
arrow={false}
|
||||||
>
|
>
|
||||||
Update Available
|
<Tag style={{ marginRight: 0 }} icon={<PersonIcon />}>
|
||||||
</Tag>
|
{!isMobile && (userProfile?.name || userProfile.username)}
|
||||||
) : null}
|
</Tag>
|
||||||
</Flex>
|
</Popover>
|
||||||
</div>
|
</Space>
|
||||||
|
) : null}
|
||||||
|
{isElectron && availableUpdate ? (
|
||||||
|
<Tag
|
||||||
|
icon={<CloudIcon />}
|
||||||
|
style={{ cursor: 'pointer', margin: '2px 0 0 0' }}
|
||||||
|
color='cyan'
|
||||||
|
onClick={() => checkForUpdates()}
|
||||||
|
>
|
||||||
|
Update Available
|
||||||
|
</Tag>
|
||||||
|
) : null}
|
||||||
|
</Flex>
|
||||||
{isOtherApp ? <DashboardWindowButtons /> : null}
|
{isOtherApp ? <DashboardWindowButtons /> : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
@ -339,7 +335,7 @@ const DashboardNavigation = () => {
|
|||||||
<>
|
<>
|
||||||
{isElectron ? (
|
{isElectron ? (
|
||||||
<Flex
|
<Flex
|
||||||
className={`ant-menu-horizontal ant-menu-light${!notificationCenterVisible ? ' electron-navigation-wrapper electrobun-webkit-app-region-drag' : ''}`}
|
className={`ant-menu-horizontal ant-menu-light${!notificationCenterVisible ? ' electron-navigation-wrapper' : ''}`}
|
||||||
style={{ lineHeight: '40px', padding: '0 2px 0 2px' }}
|
style={{ lineHeight: '40px', padding: '0 2px 0 2px' }}
|
||||||
>
|
>
|
||||||
{navigationContents}
|
{navigationContents}
|
||||||
|
|||||||
@ -39,10 +39,7 @@ const DashboardWindowButtons = () => {
|
|||||||
<div style={{ width: '80px' }} />
|
<div style={{ width: '80px' }} />
|
||||||
) : null
|
) : null
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div style={{ width: '95px', marginRight: '2.5px' }}>
|
||||||
className='electrobun-webkit-app-region-no-drag'
|
|
||||||
style={{ width: '95px', marginRight: '2.5px' }}
|
|
||||||
>
|
|
||||||
<Flex style={{ position: 'relative', zIndex: 9999 }}>
|
<Flex style={{ position: 'relative', zIndex: 9999 }}>
|
||||||
{maximizeButton}
|
{maximizeButton}
|
||||||
{minimizeButton}
|
{minimizeButton}
|
||||||
|
|||||||
@ -118,12 +118,7 @@ const ElectronProvider = ({ children }) => {
|
|||||||
|
|
||||||
if (useElectrobun) {
|
if (useElectrobun) {
|
||||||
desktopAPI.getOsInfo().then((info) => {
|
desktopAPI.getOsInfo().then((info) => {
|
||||||
if (info?.platform) {
|
if (info?.platform) setPlatform(info.platform)
|
||||||
setPlatform(info.platform)
|
|
||||||
if (info.platform === 'darwin') {
|
|
||||||
document.documentElement.classList.add('macos-vibrancy')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
desktopAPI.getWindowState().then(applyWindowState)
|
desktopAPI.getWindowState().then(applyWindowState)
|
||||||
@ -147,12 +142,7 @@ const ElectronProvider = ({ children }) => {
|
|||||||
if (!ipcRenderer) return
|
if (!ipcRenderer) return
|
||||||
|
|
||||||
ipcRenderer.invoke('os-info').then((info) => {
|
ipcRenderer.invoke('os-info').then((info) => {
|
||||||
if (info?.platform) {
|
if (info?.platform) setPlatform(info.platform)
|
||||||
setPlatform(info.platform)
|
|
||||||
if (info.platform === 'darwin') {
|
|
||||||
document.documentElement.classList.add('macos-vibrancy')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcRenderer.invoke('window-state').then(applyWindowState)
|
ipcRenderer.invoke('window-state').then(applyWindowState)
|
||||||
|
|||||||
@ -1,105 +0,0 @@
|
|||||||
import { dlopen, FFIType } from 'bun:ffi'
|
|
||||||
import { existsSync, statSync } from 'node:fs'
|
|
||||||
import path from 'node:path'
|
|
||||||
import { fileURLToPath } from 'node:url'
|
|
||||||
|
|
||||||
const MAC_TRAFFIC_LIGHTS_X = 14
|
|
||||||
const MAC_TRAFFIC_LIGHTS_Y = 12
|
|
||||||
export const MAC_WINDOW_CORNER_RADIUS = 15
|
|
||||||
|
|
||||||
function resolveDylibPath() {
|
|
||||||
const moduleDir = path.dirname(fileURLToPath(import.meta.url))
|
|
||||||
const candidates = [
|
|
||||||
path.join(moduleDir, '../bun/libMacWindowEffects.dylib'),
|
|
||||||
path.join(moduleDir, 'libMacWindowEffects.dylib')
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
if (!existsSync(candidate)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (statSync(candidate).size > 0) {
|
|
||||||
return candidate
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Ignore unreadable paths and keep searching.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyMacOSWindowEffects(mainWindow) {
|
|
||||||
if (process.platform !== 'darwin' || !mainWindow?.ptr) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const dylibPath = resolveDylibPath()
|
|
||||||
if (!dylibPath) {
|
|
||||||
console.warn(
|
|
||||||
'macOS vibrancy: native effects library not found; using transparent window only.'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const lib = dlopen(dylibPath, {
|
|
||||||
enableWindowVibrancy: {
|
|
||||||
args: [FFIType.ptr, FFIType.f64],
|
|
||||||
returns: FFIType.bool
|
|
||||||
},
|
|
||||||
ensureWindowShadow: {
|
|
||||||
args: [FFIType.ptr],
|
|
||||||
returns: FFIType.bool
|
|
||||||
},
|
|
||||||
setWindowCornerRadius: {
|
|
||||||
args: [FFIType.ptr, FFIType.f64],
|
|
||||||
returns: FFIType.bool
|
|
||||||
},
|
|
||||||
setWindowTrafficLightsPosition: {
|
|
||||||
args: [FFIType.ptr, FFIType.f64, FFIType.f64],
|
|
||||||
returns: FFIType.bool
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const vibrancyEnabled = lib.symbols.enableWindowVibrancy(
|
|
||||||
mainWindow.ptr,
|
|
||||||
MAC_WINDOW_CORNER_RADIUS
|
|
||||||
)
|
|
||||||
const shadowEnabled = lib.symbols.ensureWindowShadow(mainWindow.ptr)
|
|
||||||
const alignButtons = () =>
|
|
||||||
lib.symbols.setWindowTrafficLightsPosition(
|
|
||||||
mainWindow.ptr,
|
|
||||||
MAC_TRAFFIC_LIGHTS_X,
|
|
||||||
MAC_TRAFFIC_LIGHTS_Y
|
|
||||||
)
|
|
||||||
const refreshWindowChrome = () => {
|
|
||||||
lib.symbols.setWindowCornerRadius(
|
|
||||||
mainWindow.ptr,
|
|
||||||
MAC_WINDOW_CORNER_RADIUS
|
|
||||||
)
|
|
||||||
alignButtons()
|
|
||||||
}
|
|
||||||
|
|
||||||
const buttonsAlignedNow = alignButtons()
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
refreshWindowChrome()
|
|
||||||
}, 120)
|
|
||||||
|
|
||||||
mainWindow.on?.('resize', () => {
|
|
||||||
refreshWindowChrome()
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`macOS vibrancy applied (vibrancy=${vibrancyEnabled}, shadow=${shadowEnabled}, trafficLights=${buttonsAlignedNow}, cornerRadius=${MAC_WINDOW_CORNER_RADIUS})`
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
'macOS vibrancy: failed to apply native window effects:',
|
|
||||||
error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +1,7 @@
|
|||||||
import Electrobun, { BrowserWindow, Updater, Utils } from "electrobun/bun";
|
import Electrobun, { BrowserWindow, Updater, Utils } from "electrobun/bun";
|
||||||
import { applyApplicationMenu, setupApplicationMenuEvents } from "./menu.js";
|
import { applyApplicationMenu, setupApplicationMenuEvents } from "./menu.js";
|
||||||
import { applyMacOSWindowEffects } from "./macos-window-effects.js";
|
|
||||||
import { sendToRenderer, setMessageSender } from "./notify.js";
|
import { sendToRenderer, setMessageSender } from "./notify.js";
|
||||||
|
|
||||||
const isMacOS = process.platform === "darwin";
|
|
||||||
|
|
||||||
const DEV_SERVER_PORT = 5780;
|
const DEV_SERVER_PORT = 5780;
|
||||||
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`;
|
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`;
|
||||||
const PROTOCOL_PREFIX = "farmcontrol://";
|
const PROTOCOL_PREFIX = "farmcontrol://";
|
||||||
@ -130,7 +127,6 @@ export async function createMainWindow(rpc) {
|
|||||||
rpc,
|
rpc,
|
||||||
titleBarStyle: "hiddenInset",
|
titleBarStyle: "hiddenInset",
|
||||||
trafficLightOffset: { x: 14, y: 12 },
|
trafficLightOffset: { x: 14, y: 12 },
|
||||||
...(isMacOS ? { transparent: true } : {}),
|
|
||||||
frame: {
|
frame: {
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 800,
|
height: 800,
|
||||||
@ -139,10 +135,6 @@ export async function createMainWindow(rpc) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isMacOS) {
|
|
||||||
applyMacOSWindowEffects(mainWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupMainWindowMessaging(mainWindow);
|
setupMainWindowMessaging(mainWindow);
|
||||||
applyApplicationMenu();
|
applyApplicationMenu();
|
||||||
setupApplicationMenuEvents({
|
setupApplicationMenuEvents({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user