Is your issue REALLY a bug?
Is there an existing issue for this?
Is this issue related to iced?
What happened?
In a multi-window iced application on Windows, unfocused/background windows can fail to visually update after their state changes.
The application state updates correctly. view() is rebuilt for all iced windows. request_redraw() is also called for all native windows. However, the actual RedrawRequested / present path only runs for the focused or most recently focused window.
The result is that background windows keep showing stale content until they receive focus or Windows eventually sends them another redraw event.
This was reproduced while developing Snapdash, a cross-platform desktop widget app built with Rust + iced. Snapdash creates small transparent undecorated widget windows and updates them from Home Assistant sensor state changes.
The same application works correctly on macOS and Linux. The issue appears Windows-specific.
Environment
- OS: Windows
- App: Snapdash
- iced: fork based on
0.14.0
- Window setup:
- multiple windows
- transparent
- undecorated
- DWM shadow / rounded corners on Windows
- Backend path:
iced_winit / winit
Observed Behavior
When sensor data arrives, Snapdash updates application state and iced rebuilds the views for all windows.
Example log:
2026-04-27T14:13:16.323246Z DEBUG snapdash::app: view() called window=Id(1) theme=MacDark
2026-04-27T14:13:16.323264Z DEBUG snapdash::app: view() called window=Id(3) theme=MacDark
2026-04-27T14:13:16.323274Z DEBUG snapdash::app: view() called window=Id(2) theme=MacDark
At this point, iced/winit also requests redraw for all three native windows:
2026-04-27T14:13:16.323284Z DEBUG iced_winit::window: request_raw_redraw window=WindowId(7344172)
2026-04-27T14:13:16.323416Z DEBUG iced_winit::window: request_raw_redraw window=WindowId(21430518)
2026-04-27T14:13:16.323510Z DEBUG iced_winit::window: request_raw_redraw window=WindowId(3149896)
However, only one iced window reaches the present path:
2026-04-27T14:13:16.323638Z DEBUG iced_winit: presenting window window=Id(1)
The same pattern repeats:
2026-04-27T14:13:16.354578Z DEBUG snapdash::app: WindowRedraw event arrived window=Id(1)
2026-04-27T14:13:16.354599Z DEBUG snapdash::app: view() called window=Id(1) theme=MacDark
2026-04-27T14:13:16.354617Z DEBUG snapdash::app: view() called window=Id(3) theme=MacDark
2026-04-27T14:13:16.354626Z DEBUG snapdash::app: view() called window=Id(2) theme=MacDark
2026-04-27T14:13:16.354636Z DEBUG iced_winit::window: request_raw_redraw window=WindowId(7344172)
2026-04-27T14:13:16.354777Z DEBUG iced_winit::window: request_raw_redraw window=WindowId(21430518)
2026-04-27T14:13:16.354870Z DEBUG iced_winit::window: request_raw_redraw window=WindowId(3149896)
2026-04-27T14:13:16.354995Z DEBUG iced_winit: presenting window window=Id(1)
So from the application’s point of view:
- data arrived
- state changed
- all views were rebuilt
- all native windows were asked to redraw
- But only Id(1) presented.
Focus Changes Which Window Updates
When focus changes, the window that receives RedrawRequested / present changes too.
For example, after focusing another widget, the same pattern starts happening for Id(2):
2026-04-27T14:22:53.197897Z DEBUG snapdash::app: view() called window=Id(1) theme=MacDark
2026-04-27T14:22:53.197915Z DEBUG snapdash::app: view() called window=Id(3) theme=MacDark
2026-04-27T14:22:53.197924Z DEBUG snapdash::app: view() called window=Id(2) theme=MacDark
2026-04-27T14:22:53.197933Z DEBUG iced_winit::window: request_raw_redraw window=WindowId(414518186)
2026-04-27T14:22:53.198083Z DEBUG iced_winit::window: request_raw_redraw window=WindowId(5574518)
2026-04-27T14:22:53.198178Z DEBUG iced_winit::window: request_raw_redraw window=WindowId(23465014)
Additional diagnostic logging shows the raw WindowId to iced Id mapping:
2026-04-27T14:22:53.198297Z DEBUG iced_winit::window: redraw state before present requested=Some(Id(2)) window=Id(1) raw=WindowId(414518186) redraw_at=None
2026-04-27T14:22:53.198303Z DEBUG iced_winit::window: redraw state before present requested=Some(Id(2)) window=Id(2) raw=WindowId(5574518) redraw_at=None
2026-04-27T14:22:53.198306Z DEBUG iced_winit::window: redraw state before present requested=Some(Id(2)) window=Id(3) raw=WindowId(23465014) redraw_at=None
And only Id(2) presents:
2026-04-27T14:22:53.198319Z DEBUG iced_winit: presenting window window=Id(2)
This strongly suggests that the issue is not application state, view rebuilding, entity mapping, or request emission. The starvation happens between request_redraw() and present().
What is the expected behavior?
If multiple iced windows have updated state and request redraw, all affected windows should eventually be presented, even when they are not focused.
A background window should not need focus in order to visually update.
Actual Behavior
Only the focused or most recently focused window receives timely RedrawRequested handling and reaches present.
Other windows may have updated iced state and rebuilt views, but their swapchain/surface is not presented until focus changes or Windows later decides to deliver another redraw event.
Application-Level Workarounds Tried
Before investigating iced/winit, Snapdash tried several application-level workarounds:
- explicitly requesting redraw for all windows
- forcing redraw when sensor state changes
- forcing redraw only for the mapped widget window
- moving/nudging windows to force invalidation
- trying stronger Win32 invalidation paths
These did not reliably fix the issue, because the actual visual update depends on iced_winit entering the render/present path. The app can call request_redraw, but if Windows/winit only delivers RedrawRequested for one window, only that window gets presented.
Current Fork Workaround
In my iced fork, I added a Windows-only workaround inside the RedrawRequested handling path.
When one window receives RedrawRequested, iced first renders/presents that requested window normally. Then, on Windows only, it also draws and presents the other open windows once during the current event batch.
After this change, inactive windows update correctly.
Relevant log after the workaround:
2026-04-27T14:28:59.609267Z DEBUG iced_winit: presenting window window=Id(5)
2026-04-27T14:28:59.636150Z DEBUG iced_winit: presenting extra window window=Id(1)
2026-04-27T14:28:59.666673Z DEBUG iced_winit: presenting extra window window=Id(2)
2026-04-27T14:28:59.697869Z DEBUG iced_winit: presenting extra window window=Id(3)
Another event batch:
2026-04-27T14:28:59.729241Z DEBUG iced_winit: presenting window window=Id(3)
2026-04-27T14:28:59.760201Z DEBUG iced_winit: presenting extra window window=Id(1)
2026-04-27T14:28:59.791488Z DEBUG iced_winit: presenting extra window window=Id(2)
2026-04-27T14:28:59.822975Z DEBUG iced_winit: presenting extra window window=Id(5)
With this workaround, the application behaves correctly on Windows: background widget windows visually update as soon as sensor data changes.
To avoid a cascade where every later RedrawRequested presents all windows again, the workaround is throttled so the extra-present path runs at most once between NewEvents batches.
Why I think this seems like an iced_winit-Level Issue
The application layer does not seem to have a clean, reliable fix.
The app can:
- update state
- rebuild views
- call request_redraw
- request redraw for one window or all windows
But the visual update only happens when iced_winit reaches its render/present path.
Log shows:
view() called window=Id(1)
view() called window=Id(3)
view() called window=Id(2)
request_raw_redraw window=WindowId(...)
request_raw_redraw window=WindowId(...)
request_raw_redraw window=WindowId(...)
presenting window window=Id(2)
So iced has the updated UI, and redraw requests are emitted, but only one window is presented.
That makes this look like redraw starvation in iced_winit’s Windows event/present handling rather than a Snapdash-specific bug.
Suggested Upstream Direction
The current fork workaround is intentionally broad because it was used to prove the diagnosis. It works, but it may not be the ideal final upstream shape.
A better upstream fix could track a per-window pending redraw / pending present flag inside iced_winit.
Suggested behavior:
- When iced requests RedrawRequest::NextFrame for a window, mark that window as needing present.
- On Windows, when any WindowEvent::RedrawRequested arrives:
- render/present the window that received the event normally
- additionally render/present other windows whose pending-present flag is set
- clear the flag after successful present
- Avoid presenting completely idle windows.
Conceptually:
// Windows-only concept:
//
// When any RedrawRequested arrives:
// present(requested_window)
//
// for each other window:
// if window.needs_present:
// present(window)
// window.needs_present = false
This would avoid unnecessary presents for idle windows while still preventing background windows from being starved behind focus.
Reproduction idea:
A minimal repro could be an iced example that:
- Opens 3 transparent undecorated windows.
- Uses a timer/subscription to update visible text in all windows every second.
- Logs:
- view(window_id)
- request_redraw(raw_window_id)
- present(window_id)
- Keeps focus on one window.
Expected failure on Windows:
- all windows rebuild views
- redraw is requested for all windows
- only the focused / last-focused window presents repeatedly
- background windows visually stay stale until focused
Additional notes
After moving the workaround into iced_winit, Snapdash no longer needs application-level force redraw hacks. The app can simply update state and rely on iced to eventually present the affected windows.
This supports the idea that the fix belongs in iced_winit’s Windows redraw/present coordination.
Version
crates.io release
Operating System
Windows
Do you have any log output?
Is your issue REALLY a bug?
Is there an existing issue for this?
Is this issue related to iced?
What happened?
In a multi-window iced application on Windows, unfocused/background windows can fail to visually update after their state changes.
The application state updates correctly.
view()is rebuilt for all iced windows.request_redraw()is also called for all native windows. However, the actualRedrawRequested/presentpath only runs for the focused or most recently focused window.The result is that background windows keep showing stale content until they receive focus or Windows eventually sends them another redraw event.
This was reproduced while developing Snapdash, a cross-platform desktop widget app built with Rust + iced. Snapdash creates small transparent undecorated widget windows and updates them from Home Assistant sensor state changes.
The same application works correctly on macOS and Linux. The issue appears Windows-specific.
Environment
0.14.0iced_winit/winitObserved Behavior
When sensor data arrives, Snapdash updates application state and iced rebuilds the views for all windows.
Example log:
At this point, iced/winit also requests redraw for all three native windows:
However, only one iced window reaches the present path:
The same pattern repeats:
So from the application’s point of view:
Focus Changes Which Window Updates
When focus changes, the window that receives RedrawRequested / present changes too.
For example, after focusing another widget, the same pattern starts happening for Id(2):
Additional diagnostic logging shows the raw WindowId to iced Id mapping:
And only Id(2) presents:
This strongly suggests that the issue is not application state, view rebuilding, entity mapping, or request emission. The starvation happens between request_redraw() and present().
What is the expected behavior?
If multiple iced windows have updated state and request redraw, all affected windows should eventually be presented, even when they are not focused.
A background window should not need focus in order to visually update.
Actual Behavior
Only the focused or most recently focused window receives timely RedrawRequested handling and reaches present.
Other windows may have updated iced state and rebuilt views, but their swapchain/surface is not presented until focus changes or Windows later decides to deliver another redraw event.
Application-Level Workarounds Tried
Before investigating iced/winit, Snapdash tried several application-level workarounds:
These did not reliably fix the issue, because the actual visual update depends on iced_winit entering the render/present path. The app can call request_redraw, but if Windows/winit only delivers RedrawRequested for one window, only that window gets presented.
Current Fork Workaround
In my iced fork, I added a Windows-only workaround inside the RedrawRequested handling path.
When one window receives RedrawRequested, iced first renders/presents that requested window normally. Then, on Windows only, it also draws and presents the other open windows once during the current event batch.
After this change, inactive windows update correctly.
Relevant log after the workaround:
Another event batch:
With this workaround, the application behaves correctly on Windows: background widget windows visually update as soon as sensor data changes.
To avoid a cascade where every later RedrawRequested presents all windows again, the workaround is throttled so the extra-present path runs at most once between NewEvents batches.
Why I think this seems like an iced_winit-Level Issue
The application layer does not seem to have a clean, reliable fix.
The app can:
But the visual update only happens when iced_winit reaches its render/present path.
Log shows:
So iced has the updated UI, and redraw requests are emitted, but only one window is presented.
That makes this look like redraw starvation in iced_winit’s Windows event/present handling rather than a Snapdash-specific bug.
Suggested Upstream Direction
The current fork workaround is intentionally broad because it was used to prove the diagnosis. It works, but it may not be the ideal final upstream shape.
A better upstream fix could track a per-window pending redraw / pending present flag inside iced_winit.
Suggested behavior:
Conceptually:
This would avoid unnecessary presents for idle windows while still preventing background windows from being starved behind focus.
Reproduction idea:
A minimal repro could be an iced example that:
Expected failure on Windows:
Additional notes
After moving the workaround into iced_winit, Snapdash no longer needs application-level force redraw hacks. The app can simply update state and rely on iced to eventually present the affected windows.
This supports the idea that the fix belongs in iced_winit’s Windows redraw/present coordination.
Version
crates.io release
Operating System
Windows
Do you have any log output?