From f16751577c94ec072390e91079d2db736565c706 Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Fri, 15 Nov 2024 19:34:49 -0800 Subject: [PATCH] feat: initial push support for windows Signed-off-by: Sam Gammon --- Cargo.toml | 2 + src/event_loop.rs | 14 +++- src/platform/windows.rs | 85 +++++++++++++++++++++++++ src/platform_impl/windows/event_loop.rs | 23 ++++++- src/push.rs | 4 ++ 5 files changed, 126 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ea61ed2c..d7839400c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,11 +62,13 @@ parking_lot = "0.12" unicode-segmentation = "1.11" windows-version = "0.1" windows-core = "0.58" +windows-async = "0.1.1" [target."cfg(target_os = \"windows\")".dependencies.windows] version = "0.58" features = [ "implement", + "Networking_PushNotifications", "Win32_Devices_HumanInterfaceDevice", "Win32_Foundation", "Win32_Globalization", diff --git a/src/event_loop.rs b/src/event_loop.rs index c27578d39..e99a62392 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -15,7 +15,7 @@ //! [send_event]: crate::event_loop::EventLoopProxy::send_event use std::time::Instant; use std::{error, fmt, marker::PhantomData, ops::Deref}; - +use windows::Networking::PushNotifications::PushNotificationChannel; use crate::{ dpi::PhysicalPosition, error::ExternalError, @@ -319,6 +319,18 @@ impl EventLoopWindowTarget { ))] self.p.set_theme(theme) } + + /// Sets the push channel for the application. + /// + /// ## Platform-specific + /// + /// - **Windows only.** Other platforms deliver the push channel or token via other means. + #[cfg(any(windows))] + #[cfg(feature = "push-notifications")] + #[inline] + pub fn set_push_channel(&self, channel: Option) { + self.p.set_push_channel(channel) + } } #[cfg(feature = "rwh_05")] diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 59d0d7c3d..4931c4199 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -17,6 +17,18 @@ use crate::{ }; use windows::Win32::UI::Input::KeyboardAndMouse::*; +#[cfg(feature = "push-notifications")] +use windows::Networking::PushNotifications::{ + PushNotificationChannel, + PushNotificationChannelManager, +}; + +#[cfg(feature = "push-notifications")] +use { + windows::Foundation::AsyncStatus, + windows::Foundation::IAsyncOperation, +}; + pub type HWND = isize; pub type HMENU = isize; @@ -440,3 +452,76 @@ impl IconExtWindows for Icon { Ok(Icon { inner: win_icon }) } } + +/// Additional methods on `PushNotifications` that are specific to Windows. +#[cfg(feature = "push-notifications")] +pub trait PushNotificationsExtWindows { + /// Setup Windows Notifications System, the Windows equivalent to APNS. + /// + /// This call yields an `IAsyncOperation` which must be awaited to obtain the underlying + /// `PushNotificationChannel`. + /// + /// Return error codes are documented below: + /// (None yet). + fn setup_wns() -> Result<(), u8> { + let mgr = PushNotificationChannelManager::GetDefault(); + let mgr = match mgr { + Ok(mgr) => mgr, + Err(_) => { + return Err(1); + } + }; + let push_channel_op = match + mgr.CreatePushNotificationChannelForApplicationAsync() { + Ok(channel) => channel, + Err(_) => { + return Err(2); + } + }; + // Attach callback + attach_callback(push_channel_op, |result| { + match result { + Ok(value) => register_push_channel(value), + Err(e) => println!("Operation failed with error: {:?}", e), + } + }).expect("failed to attach callback for windows push notification token"); + + Ok(()) + } +} + +#[cfg(feature = "push-notifications")] +fn register_push_channel(_channel: PushNotificationChannel) { + eprintln!("would register channel") +} + +#[cfg(feature = "push-notifications")] +fn attach_callback(operation: IAsyncOperation, callback: F) -> windows::core::Result<()> +where + T: windows::core::RuntimeType + 'static, + F: FnOnce(windows::core::Result) + Send + Clone + Copy + 'static, +{ + unsafe { + operation.SetCompleted( + &windows::Foundation::AsyncOperationCompletedHandler::new( + move |op, _| { + let result = match op.unwrap().Status()? { + AsyncStatus::Completed => Ok(op.unwrap().GetResults()), + AsyncStatus::Canceled => Err(windows::core::Error:: new::( + windows::core::HRESULT(0x800704C7u32 as i32), // Operation canceled + "Operation was canceled".into(), + )), + AsyncStatus::Error => Err(windows::core::Error::new::( + op.unwrap().ErrorCode().unwrap(), // Operation failed + "Operation failed".into(), + )), + AsyncStatus::Started => unreachable!(), + _ => unreachable!(), + }; + callback(result.expect("empty waiter")); + Ok(()) + }, + ), + ) + } +} diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index caf53928c..189d0207f 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -42,7 +42,6 @@ use windows::{ }, }, }; - use crate::{ dpi::{PhysicalPosition, PhysicalSize, PixelUnit}, error::ExternalError, @@ -66,6 +65,11 @@ use crate::{ }; use runner::{EventLoopRunner, EventLoopRunnerShared}; +#[cfg(feature = "push-notifications")] +use { + windows::Networking::PushNotifications::PushNotificationChannel, +}; + type GetPointerFrameInfoHistory = unsafe extern "system" fn( pointerId: u32, entriesCount: *mut u32, @@ -144,6 +148,8 @@ pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) dpi_aware: bool, pub(crate) msg_hook: Option bool + 'static>>, pub(crate) preferred_theme: Option, + #[cfg(feature = "push-notifications")] + pub(crate) push_channel: Option, } impl Default for PlatformSpecificEventLoopAttributes { @@ -153,6 +159,8 @@ impl Default for PlatformSpecificEventLoopAttributes { dpi_aware: true, msg_hook: None, preferred_theme: None, + #[cfg(feature = "push-notifications")] + push_channel: None, } } } @@ -163,6 +171,8 @@ pub struct EventLoopWindowTarget { thread_msg_target: HWND, pub(crate) preferred_theme: Arc>>, pub(crate) runner_shared: EventLoopRunnerShared, + #[cfg(feature = "push-notifications")] + pub(crate) push_channel: Arc>>, } impl EventLoop { @@ -203,6 +213,8 @@ impl EventLoop { thread_msg_target, runner_shared, preferred_theme: Arc::new(Mutex::new(attributes.preferred_theme)), + #[cfg(feature = "push-notifications")] + push_channel: Arc::new(Mutex::new(None)), }, _marker: PhantomData, }, @@ -338,6 +350,15 @@ impl EventLoopWindowTarget { let _ = unsafe { SendMessageW(window, *CHANGE_THEME_MSG_ID, WPARAM(0), LPARAM(0)) }; }); } + + #[cfg(feature = "push-notifications")] + #[inline] + pub fn set_push_channel(&self, channel: Option) { + *self.push_channel.lock() = channel; + self.runner_shared.owned_windows(|window| { + let _ = unsafe { SendMessageW(window, *CHANGE_THEME_MSG_ID, WPARAM(0), LPARAM(0)) }; + }); + } } fn main_thread_id() -> u32 { diff --git a/src/push.rs b/src/push.rs index eaac14e70..ae90ce96b 100644 --- a/src/push.rs +++ b/src/push.rs @@ -13,3 +13,7 @@ /// The push token type. pub type PushToken = Vec; + +/// Push notifications features and utilities. On most platforms, this consists of obtaining +/// the token (which may be triggered at app start), then exposing it to the developer. +pub trait PushNotifications {}