Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/windowing/winit/windows/network.rs
1use crate::windowing::winit::app::CustomEvent;
2use anyhow::Context;
3use windows::core::{implement, Interface};
4use windows::Win32::Networking::NetworkListManager::{
5 INetworkListManager, INetworkListManagerEvents, INetworkListManagerEvents_Impl,
6 NetworkListManager, NLM_CONNECTIVITY, NLM_CONNECTIVITY_DISCONNECTED,
7 NLM_CONNECTIVITY_IPV4_INTERNET, NLM_CONNECTIVITY_IPV6_INTERNET,
8};
9use windows::Win32::System::Com::{
10 CoCreateInstance, CoInitializeEx, IConnectionPoint, IConnectionPointContainer, CLSCTX_ALL,
11 COINIT_APARTMENTTHREADED,
12};
13 
14/// Implements the INetworkListManagerEvents trait so we can pass along connectivity events from Windows
15/// OS to our winit event loop.
16#[implement(INetworkListManagerEvents)]
17#[allow(non_camel_case_types)]
18struct WindowsNetworkListener {
19 event_loop: winit::event_loop::EventLoopProxy<CustomEvent>,
20}
21 
22impl WindowsNetworkListener {
23 fn new(event_loop: winit::event_loop::EventLoopProxy<CustomEvent>) -> Self {
24 Self { event_loop }
25 }
26}
27 
28#[allow(non_snake_case)]
29impl INetworkListManagerEvents_Impl for WindowsNetworkListener_Impl {
30 fn ConnectivityChanged(&self, new_connectivity: NLM_CONNECTIVITY) -> windows::core::Result<()> {
31 // The NLM_CONNECTIVITY parameter is a bitmap. When it contains NLM_CONNECTIVITY_IPV4_INTERNET
32 // or NLM_CONNECTIVITY_IPV6_INTERNET, there's a connection. When it's equal to
33 // NLM_CONNECTIVITY_DISCONNECTED, it's a disconnection. Other arbitrary network events are ignored.
34 // https://learn.microsoft.com/en-us/windows/win32/api/netlistmgr/ne-netlistmgr-nlm_connectivity#syntax
35 // let connected = new_connectivity.eq(&NLM_CONNECTIVITY_IPV6_INTERNET) || new_connectivity.eq(&NLM_CONNECTIVITY_IPV4_INTERNET);
36 let connected = (new_connectivity.0
37 & (NLM_CONNECTIVITY_IPV6_INTERNET.0 | NLM_CONNECTIVITY_IPV4_INTERNET.0))
38 != 0;
39 let disconnected = new_connectivity.eq(&NLM_CONNECTIVITY_DISCONNECTED);
40 
41 if connected {
42 let _ = self.event_loop.send_event(CustomEvent::InternetConnected);
43 } else if disconnected {
44 let _ = self
45 .event_loop
46 .send_event(CustomEvent::InternetDisconnected);
47 }
48 Ok(())
49 }
50}
51 
52pub struct WindowsNetworkConnectionPoint {
53 connection_point: IConnectionPoint,
54 cookie: u32,
55 
56 #[allow(unused)]
57 /// We keep the events interface around for the duration of the program because
58 /// we're not sure we don't need it to keep living.
59 events_interface: INetworkListManagerEvents,
60}
61 
62impl WindowsNetworkConnectionPoint {
63 pub fn clean_up(&self) {
64 unsafe {
65 if let Err(e) = self.connection_point.Unadvise(self.cookie) {
66 log::warn!("Failed to clean up network connection point: {e:?}");
67 }
68 }
69 }
70}
71 
72pub fn add_network_connection_listener(
73 event_loop_proxy: winit::event_loop::EventLoopProxy<CustomEvent>,
74) -> anyhow::Result<WindowsNetworkConnectionPoint> {
75 let network_listener = {
76 unsafe {
77 // This invocation matches winit exactly. We want to make sure we don't modify any winit invariants in the case that
78 // winit also calls CoInitializeEx.
79 // https://github.com/rust-windowing/winit/blob/953d9b426886749e2f88250f420c87db58080c97/src/platform_impl/windows/window.rs#L1386
80 CoInitializeEx(None, COINIT_APARTMENTTHREADED)
81 .ok()
82 .context("Failed to initialize COM")?;
83 
84 let events_interface: INetworkListManagerEvents =
85 WindowsNetworkListener::new(event_loop_proxy).into();
86 
87 let connection_point_container: IConnectionPointContainer =
88 CoCreateInstance(&NetworkListManager, None, CLSCTX_ALL)
89 .and_then(|network_manager: INetworkListManager| network_manager.cast())
90 .context("Failed to construct IConnectionPointContainer")?;
91 
92 let connection_point: IConnectionPoint = connection_point_container
93 .FindConnectionPoint(&INetworkListManagerEvents::IID)
94 .context("Failed to construct IConnectionPoint")?;
95 
96 let cookie = connection_point
97 .Advise(&events_interface)
98 .context("Failed to attach point and sink")?;
99 
100 WindowsNetworkConnectionPoint {
101 connection_point,
102 cookie,
103 events_interface,
104 }
105 }
106 };
107 Ok(network_listener)
108}
109