Skip to content

Commit 42308c5

Browse files
committed
build 0.0.6.3 new heave weather and fix app freese post pc sleep/hibernate
1 parent 9c49312 commit 42308c5

12 files changed

Lines changed: 199 additions & 7 deletions

File tree

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,11 @@ There are two ways to fix this:
137137

138138
Windows will automatically use this DLL to translate OpenGL hardware calls into software rendering, allowing the app to work flawlessly on any PC regardless of graphics drivers.
139139

140-
*Note: A `-software` flag is also available (`.\weatherwidget.exe -software`) which instructs the Fyne framework to prefer software rendering, but this still requires basic OpenGL driver availability at the OS level.*
140+
*Note: A `-software` flag is also available (`.\weatherwidget.exe -software`) which instructs the Fyne framework to prefer software rendering, but this still requires basic OpenGL driver availability at the OS level.*
141+
142+
## Changelog
143+
144+
### v0.0.6.3 — 2025-06-26
145+
146+
- Added new "heavy rain" weather icon (`heavy_rain.png`) for heavy intensity rain conditions across all providers (OWM, WU, EWW)
147+
- Fixed weather and time display staying frozen after PC wakes from sleep/hibernation — the widget now detects system resume and triggers an immediate refresh

assets/icons/heavy_rain.png

21.9 KB
Loading

go.work

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
go 1.25.0
2+
3+
use .

go.work.sum

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
2+
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
3+
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
4+
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
5+
github.com/jackmordaunt/icns/v2 v2.2.6/go.mod h1:DqlVnR5iafSphrId7aSD06r3jg0KRC9V6lEBBp504ZQ=
6+
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
7+
github.com/lucor/goinfo v0.9.0/go.mod h1:L6m6tN5Rlova5Z83h1ZaKsMP1iiaoZ9vGTNzu5QKOD4=
8+
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
9+
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
10+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
11+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
12+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
13+
github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
14+
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
15+
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc=
16+
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
17+
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
18+
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
19+
golang.org/x/tools/go/vcs v0.1.0-deprecated/go.mod h1:zUrvATBAvEI9535oC0yWYsLsHIV4Z7g63sNPVMtuBy8=

images/heavy-rain.png

21.9 KB
Loading

internal/app/manager.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"weatherwidget/internal/config"
1414
"weatherwidget/internal/guard"
1515
"weatherwidget/internal/i18n"
16+
"weatherwidget/internal/power"
1617
"weatherwidget/internal/scheduler"
1718
"weatherwidget/internal/ui"
1819
"weatherwidget/internal/weather"
@@ -126,6 +127,16 @@ func (a *AppManager) Run() error {
126127
})
127128
a.scheduler.Start()
128129

130+
// 10. Listen for system resume (wake from sleep/hibernate) and trigger
131+
// an immediate weather refresh so the widget doesn't stay stale.
132+
go func() {
133+
resumeCh := power.ResumeNotifier()
134+
for range resumeCh {
135+
log.Printf("system resumed from sleep — triggering immediate weather refresh")
136+
a.scheduler.FetchNow()
137+
}
138+
}()
139+
129140
return nil
130141
}
131142

internal/power/power_other.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//go:build !windows
2+
3+
package power
4+
5+
// ResumeNotifier returns a channel that receives a value when the system
6+
// resumes from sleep or hibernation. On non-Windows platforms this is a
7+
// no-op: the returned channel will never receive.
8+
func ResumeNotifier() <-chan struct{} {
9+
return make(chan struct{})
10+
}

internal/power/power_windows.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//go:build windows
2+
3+
// Package power provides system power-state notifications.
4+
// On Windows it listens for WM_POWERBROADCAST / PBT_APMRESUMESUSPEND
5+
// so the application can react immediately when the PC wakes from
6+
// sleep or hibernation.
7+
package power
8+
9+
import (
10+
"log"
11+
"syscall"
12+
"unsafe"
13+
14+
"golang.org/x/sys/windows"
15+
)
16+
17+
// Win32 constants for power broadcast messages.
18+
const (
19+
wmPowerBroadcast = 0x0218
20+
pbtAPMResumeSuspend = 0x0007
21+
pbtAPMResumeAuto = 0x0012
22+
)
23+
24+
// Window class/procedure related Win32 calls.
25+
var (
26+
modUser32 = windows.NewLazySystemDLL("user32.dll")
27+
procRegisterClassW = modUser32.NewProc("RegisterClassExW")
28+
procCreateWindowExW = modUser32.NewProc("CreateWindowExW")
29+
procGetMessageW = modUser32.NewProc("GetMessageW")
30+
procDefWindowProcW = modUser32.NewProc("DefWindowProcW")
31+
)
32+
33+
// WNDCLASSEXW mirrors the Win32 WNDCLASSEX structure.
34+
type wndClassExW struct {
35+
CbSize uint32
36+
Style uint32
37+
LpfnWndProc uintptr
38+
CbClsExtra int32
39+
CbWndExtra int32
40+
HInstance windows.Handle
41+
HIcon windows.Handle
42+
HCursor windows.Handle
43+
HbrBackground windows.Handle
44+
LpszMenuName *uint16
45+
LpszClassName *uint16
46+
HIconSm windows.Handle
47+
}
48+
49+
// msg mirrors the Win32 MSG structure.
50+
type msg struct {
51+
Hwnd uintptr
52+
Message uint32
53+
WParam uintptr
54+
LParam uintptr
55+
Time uint32
56+
Pt struct{ X, Y int32 }
57+
}
58+
59+
// resumeCh is the package-level channel shared with the wndproc callback.
60+
var resumeCh chan struct{}
61+
62+
// ResumeNotifier creates a hidden window that listens for power broadcast
63+
// messages and returns a channel that receives a value each time the system
64+
// resumes from sleep or hibernation.
65+
//
66+
// The returned channel is buffered (size 1) so a resume event is never lost
67+
// even if the consumer is briefly busy. Call this once at application startup.
68+
func ResumeNotifier() <-chan struct{} {
69+
resumeCh = make(chan struct{}, 1)
70+
go listenPowerEvents()
71+
return resumeCh
72+
}
73+
74+
// listenPowerEvents registers a hidden message-only window and pumps messages.
75+
func listenPowerEvents() {
76+
className, _ := syscall.UTF16PtrFromString("WeatherWidgetPowerWnd")
77+
78+
wc := wndClassExW{
79+
LpfnWndProc: syscall.NewCallback(powerWndProc),
80+
LpszClassName: className,
81+
}
82+
wc.CbSize = uint32(unsafe.Sizeof(wc))
83+
84+
ret, _, err := procRegisterClassW.Call(uintptr(unsafe.Pointer(&wc)))
85+
if ret == 0 {
86+
log.Printf("power: RegisterClassExW failed: %v", err)
87+
return
88+
}
89+
90+
// HWND_MESSAGE (-3) creates a message-only window (no visible UI).
91+
hwndMessage := uintptr(^uintptr(2)) // -3 as uintptr
92+
hwnd, _, err := procCreateWindowExW.Call(
93+
0,
94+
uintptr(unsafe.Pointer(className)),
95+
0,
96+
0, 0, 0, 0, 0,
97+
hwndMessage, 0, 0, 0,
98+
)
99+
if hwnd == 0 {
100+
log.Printf("power: CreateWindowExW failed: %v", err)
101+
return
102+
}
103+
104+
log.Printf("power: listening for resume events (hwnd=%#x)", hwnd)
105+
106+
// Message pump — blocks forever (runs in its own goroutine).
107+
var m msg
108+
for {
109+
ret, _, _ := procGetMessageW.Call(
110+
uintptr(unsafe.Pointer(&m)),
111+
hwnd, 0, 0,
112+
)
113+
if ret == 0 || ret == ^uintptr(0) {
114+
// WM_QUIT or error
115+
return
116+
}
117+
}
118+
}
119+
120+
// powerWndProc handles window messages for the hidden power-listener window.
121+
func powerWndProc(hwnd uintptr, umsg uint32, wparam, lparam uintptr) uintptr {
122+
if umsg == wmPowerBroadcast {
123+
switch wparam {
124+
case pbtAPMResumeSuspend, pbtAPMResumeAuto:
125+
log.Printf("power: system resumed from sleep/hibernate (event=%#x)", wparam)
126+
// Non-blocking send — drop if channel already has a pending signal.
127+
select {
128+
case resumeCh <- struct{}{}:
129+
default:
130+
}
131+
}
132+
}
133+
134+
ret, _, _ := procDefWindowProcW.Call(hwnd, uintptr(umsg), wparam, lparam)
135+
return ret
136+
}

internal/weather/remoteapi/adapter.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ func mapOWMConditionToIcon(id int) string {
206206
return weather.IconStorm
207207
case id >= 300 && id <= 399:
208208
return weather.IconRain
209+
case id == 502 || id == 503 || id == 504:
210+
return weather.IconHeavyRain
209211
case id >= 500 && id <= 599:
210212
return weather.IconRain
211213
case id >= 600 && id <= 699:
@@ -354,7 +356,7 @@ func (r *RemoteAPIAdapter) testWU(ctx context.Context) error {
354356
func deriveWUCondition(obs wuObservation) (icon, description string) {
355357
switch {
356358
case obs.Metric.PrecipRate > 5:
357-
return weather.IconStorm, "Heavy Rain"
359+
return weather.IconHeavyRain, "Heavy Rain"
358360
case obs.Metric.PrecipRate > 0:
359361
return weather.IconRain, "Rain"
360362
case obs.Metric.WindSpeed > 50:
@@ -510,6 +512,8 @@ func mapEWWFreeTextToIcon(freeText string) string {
510512
return weather.IconStorm
511513
case strings.Contains(lower, "snow"):
512514
return weather.IconSnow
515+
case strings.Contains(lower, "heavy") && strings.Contains(lower, "rain"):
516+
return weather.IconHeavyRain
513517
case strings.Contains(lower, "rain"), strings.Contains(lower, "drizzle"):
514518
return weather.IconRain
515519
case strings.Contains(lower, "fog"), strings.Contains(lower, "mist"), strings.Contains(lower, "haze"):

internal/weather/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
IconCloudy = "cloudy"
1717
IconCloudyMoon = "cloudy_moon"
1818
IconRain = "rain"
19+
IconHeavyRain = "heavy_rain"
1920
IconSnow = "snow"
2021
IconStorm = "storm"
2122
IconFog = "fog"
@@ -29,6 +30,7 @@ var AllIconCodes = []string{
2930
IconCloudy,
3031
IconCloudyMoon,
3132
IconRain,
33+
IconHeavyRain,
3234
IconSnow,
3335
IconStorm,
3436
IconFog,

0 commit comments

Comments
 (0)