Skip to content

Commit 3cbdf71

Browse files
author
Ricardo Wagemaker
committed
build 0.0.7 MacOS enhancements
1 parent 0297a33 commit 3cbdf71

7 files changed

Lines changed: 226 additions & 7 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,10 @@ There are two ways to fix this:
145145

146146
- Added new "heavy rain" weather icon (`heavy_rain.png`) for heavy intensity rain conditions across all providers (OWM, WU, EWW)
147147
- Fixed weather and time display staying frozen after PC wakes from sleep/hibernation — the widget now detects system resume and triggers an immediate refresh
148+
149+
### v0.0.7 — 2025-07-03
150+
151+
- **macOS: Rounded corners** — Widget window now renders with 12pt rounded corners matching macOS design conventions
152+
- **macOS: Window transparency** — Background transparency slider (25%/50%/75%/100%) now works on macOS using `NSWindow.alphaValue` with remapped values to keep content readable
153+
- **macOS: Auto-start at login** — Added LaunchAgent support so the "Launch WeatherWidget when starts" setting works on macOS (creates `~/Library/LaunchAgents/com.weatherwidget.app.plist`)
154+
- **macOS: Build script** — Added `build-darwin.sh` for one-command universal .app + .dmg creation

build-darwin.sh

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# WeatherWidget macOS Build Script
5+
# Builds a universal .app bundle and optionally a .dmg
6+
7+
APP_NAME="WeatherWidget"
8+
BINARY_NAME="weatherwidget"
9+
BUNDLE_ID="com.weatherwidget"
10+
CMD_PATH="./cmd/weatherwidget/"
11+
APP_ICON="assets/icons/clear.png"
12+
VERSION="${1:-dev}"
13+
14+
echo "==> Building WeatherWidget $VERSION for macOS..."
15+
16+
# Build both architectures
17+
echo " Compiling darwin/amd64..."
18+
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -v -ldflags="-s -w -X main.version=$VERSION" -o "$BINARY_NAME-darwin-amd64" "$CMD_PATH"
19+
20+
echo " Compiling darwin/arm64..."
21+
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -v -ldflags="-s -w -X main.version=$VERSION" -o "$BINARY_NAME-darwin-arm64" "$CMD_PATH"
22+
23+
# Assemble .app bundle
24+
APP_BUNDLE="$APP_NAME-$VERSION.app"
25+
echo "==> Assembling $APP_BUNDLE..."
26+
27+
rm -rf "$APP_BUNDLE"
28+
mkdir -p "$APP_BUNDLE/Contents/MacOS"
29+
mkdir -p "$APP_BUNDLE/Contents/Resources"
30+
31+
# Universal binary
32+
lipo -create -output "$APP_BUNDLE/Contents/MacOS/$BINARY_NAME" \
33+
"$BINARY_NAME-darwin-amd64" \
34+
"$BINARY_NAME-darwin-arm64"
35+
chmod +x "$APP_BUNDLE/Contents/MacOS/$BINARY_NAME"
36+
37+
# Build .icns
38+
ICONSET="/tmp/$APP_NAME.iconset"
39+
rm -rf "$ICONSET"
40+
mkdir -p "$ICONSET"
41+
sips -z 16 16 "$APP_ICON" --out "$ICONSET/icon_16x16.png" > /dev/null
42+
sips -z 32 32 "$APP_ICON" --out "$ICONSET/icon_16x16@2x.png" > /dev/null
43+
sips -z 32 32 "$APP_ICON" --out "$ICONSET/icon_32x32.png" > /dev/null
44+
sips -z 64 64 "$APP_ICON" --out "$ICONSET/icon_32x32@2x.png" > /dev/null
45+
sips -z 128 128 "$APP_ICON" --out "$ICONSET/icon_128x128.png" > /dev/null
46+
sips -z 256 256 "$APP_ICON" --out "$ICONSET/icon_128x128@2x.png" > /dev/null
47+
sips -z 256 256 "$APP_ICON" --out "$ICONSET/icon_256x256.png" > /dev/null
48+
sips -z 512 512 "$APP_ICON" --out "$ICONSET/icon_256x256@2x.png" > /dev/null
49+
cp "$APP_ICON" "$ICONSET/icon_512x512.png"
50+
sips -z 512 512 "$APP_ICON" --out "$ICONSET/icon_512x512@2x.png" > /dev/null
51+
iconutil -c icns "$ICONSET" -o "$APP_BUNDLE/Contents/Resources/$APP_NAME.icns"
52+
rm -rf "$ICONSET"
53+
54+
# Write Info.plist
55+
cat > "$APP_BUNDLE/Contents/Info.plist" <<EOF
56+
<?xml version="1.0" encoding="UTF-8"?>
57+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
58+
<plist version="1.0">
59+
<dict>
60+
<key>CFBundleName</key>
61+
<string>$APP_NAME</string>
62+
<key>CFBundleDisplayName</key>
63+
<string>$APP_NAME</string>
64+
<key>CFBundleIdentifier</key>
65+
<string>$BUNDLE_ID</string>
66+
<key>CFBundleVersion</key>
67+
<string>$VERSION</string>
68+
<key>CFBundleShortVersionString</key>
69+
<string>$VERSION</string>
70+
<key>CFBundleExecutable</key>
71+
<string>$BINARY_NAME</string>
72+
<key>CFBundleIconFile</key>
73+
<string>$APP_NAME</string>
74+
<key>CFBundlePackageType</key>
75+
<string>APPL</string>
76+
<key>CFBundleSignature</key>
77+
<string>????</string>
78+
<key>NSHighResolutionCapable</key>
79+
<true/>
80+
<key>NSSupportsAutomaticGraphicsSwitching</key>
81+
<true/>
82+
<key>LSMinimumSystemVersion</key>
83+
<string>11.0</string>
84+
<key>LSUIElement</key>
85+
<false/>
86+
</dict>
87+
</plist>
88+
EOF
89+
90+
echo "==> Created: $APP_BUNDLE"
91+
92+
# Build .dmg
93+
DMG_NAME="$APP_NAME-$VERSION.dmg"
94+
echo "==> Creating $DMG_NAME..."
95+
rm -f "$DMG_NAME"
96+
hdiutil create -volname "$APP_NAME $VERSION" \
97+
-srcfolder "$APP_BUNDLE" \
98+
-ov -format UDZO \
99+
-o "$DMG_NAME"
100+
101+
echo ""
102+
echo "==> Build complete!"
103+
echo " App: $APP_BUNDLE"
104+
echo " DMG: $DMG_NAME"
105+
echo ""
106+
echo " To install: open $DMG_NAME and drag to /Applications"
107+
108+
# Cleanup intermediate binaries
109+
rm -f "$BINARY_NAME-darwin-amd64" "$BINARY_NAME-darwin-arm64"

internal/ui/autostart_darwin.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//go:build darwin
2+
3+
package ui
4+
5+
import (
6+
"fmt"
7+
"log"
8+
"os"
9+
"path/filepath"
10+
)
11+
12+
const launchAgentLabel = "com.weatherwidget.app"
13+
14+
// launchAgentPath returns ~/Library/LaunchAgents/com.weatherwidget.app.plist.
15+
func launchAgentPath() string {
16+
home, err := os.UserHomeDir()
17+
if err != nil {
18+
home = "."
19+
}
20+
return filepath.Join(home, "Library", "LaunchAgents", launchAgentLabel+".plist")
21+
}
22+
23+
// launchAgentPlist returns the plist XML content for a LaunchAgent that
24+
// starts the given executable at login.
25+
func launchAgentPlist(exePath string) string {
26+
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
27+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
28+
<plist version="1.0">
29+
<dict>
30+
<key>Label</key>
31+
<string>%s</string>
32+
<key>ProgramArguments</key>
33+
<array>
34+
<string>%s</string>
35+
</array>
36+
<key>RunAtLoad</key>
37+
<true/>
38+
<key>KeepAlive</key>
39+
<false/>
40+
</dict>
41+
</plist>
42+
`, launchAgentLabel, exePath)
43+
}
44+
45+
// isAutoStartEnabled checks whether the LaunchAgent plist exists.
46+
func isAutoStartEnabled() bool {
47+
_, err := os.Stat(launchAgentPath())
48+
return err == nil
49+
}
50+
51+
// setAutoStartEnabled creates or removes the LaunchAgent plist.
52+
// When enabled, it writes a plist that tells launchd to run the current
53+
// executable at login. When disabled, it removes the plist file.
54+
func setAutoStartEnabled(enabled bool) error {
55+
path := launchAgentPath()
56+
57+
if enabled {
58+
exePath, err := os.Executable()
59+
if err != nil {
60+
return err
61+
}
62+
exePath, err = filepath.EvalSymlinks(exePath)
63+
if err != nil {
64+
return err
65+
}
66+
67+
dir := filepath.Dir(path)
68+
if err := os.MkdirAll(dir, 0755); err != nil {
69+
return err
70+
}
71+
72+
content := launchAgentPlist(exePath)
73+
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
74+
return err
75+
}
76+
log.Printf("macOS: auto-start enabled: %s", path)
77+
return nil
78+
}
79+
80+
// Remove the plist.
81+
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
82+
return err
83+
}
84+
log.Printf("macOS: auto-start disabled")
85+
return nil
86+
}

internal/ui/autostart_other.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build !windows && !linux
1+
//go:build !windows && !linux && !darwin
22

33
package ui
44

internal/ui/win32_darwin.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,27 @@ static void setupDarwinWindow(uintptr_t winHandle) {
107107
// OpenGL framebuffer is fully opaque — there is no way to make just the
108108
// background transparent while keeping text at full opacity with a single
109109
// GL surface. The entire window (including content) fades together.
110+
//
111+
// To keep content readable, we remap the user-facing opacity values to a
112+
// narrower alphaValue range:
113+
// 25% → 0.55 (semi-transparent but text still legible)
114+
// 50% → 0.70
115+
// 75% → 0.85
116+
// 100% → 1.00
110117
static void setDarwinBackgroundAlpha(uintptr_t winHandle, int opacityPercent) {
111118
dispatch_async(dispatch_get_main_queue(), ^{
112119
NSWindow *w = (__bridge NSWindow*)(void*)winHandle;
113-
CGFloat alpha = (CGFloat)opacityPercent / 100.0;
120+
// Map [25, 100] → [0.55, 1.0] linearly.
121+
// For values below 25, clamp to 0.55.
122+
CGFloat alpha;
123+
if (opacityPercent >= 100) {
124+
alpha = 1.0;
125+
} else if (opacityPercent <= 25) {
126+
alpha = 0.55;
127+
} else {
128+
// Linear interpolation: 25→0.55, 100→1.0
129+
alpha = 0.55 + (CGFloat)(opacityPercent - 25) * (0.45 / 75.0);
130+
}
114131
[w setAlphaValue:alpha];
115132
});
116133
}

release

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.0.6.3
1+
0.0.7

winres/winres.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@
2323
"#1": {
2424
"0409": {
2525
"fixed": {
26-
"file_version": "0.0.6.3",
27-
"product_version": "0.0.6.3"
26+
"file_version": "0.0.7",
27+
"product_version": "0.0.7"
2828
},
2929
"info": {
3030
"0409": {
3131
"CompanyName": "Ricardo Wagemaker",
3232
"FileDescription": "Weather Widget Desktop Application",
33-
"FileVersion": "0.0.6.3",
33+
"FileVersion": "0.0.7",
3434
"InternalName": "weatherwidget",
3535
"LegalCopyright": "© 2026 Ricardo Wagemaker",
3636
"OriginalFilename": "weatherwidget.exe",
3737
"ProductName": "Weather Widget",
38-
"ProductVersion": "0.0.6.3"
38+
"ProductVersion": "0.0.7"
3939
}
4040
}
4141
}

0 commit comments

Comments
 (0)