Skip to content

Commit

Permalink
feat: initial push support for windows
Browse files Browse the repository at this point in the history
Signed-off-by: Sam Gammon <[email protected]>
  • Loading branch information
sgammon committed Nov 16, 2024
1 parent 11854b2 commit f167515
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 13 additions & 1 deletion src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -319,6 +319,18 @@ impl<T> EventLoopWindowTarget<T> {
))]
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<PushNotificationChannel>) {
self.p.set_push_channel(channel)
}
}

#[cfg(feature = "rwh_05")]
Expand Down
85 changes: 85 additions & 0 deletions src/platform/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<T, F>(operation: IAsyncOperation<T>, callback: F) -> windows::core::Result<()>
where
T: windows::core::RuntimeType + 'static,
F: FnOnce(windows::core::Result<T>) + 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::<String>(
windows::core::HRESULT(0x800704C7u32 as i32), // Operation canceled
"Operation was canceled".into(),
)),
AsyncStatus::Error => Err(windows::core::Error::new::<String>(
op.unwrap().ErrorCode().unwrap(), // Operation failed
"Operation failed".into(),
)),
AsyncStatus::Started => unreachable!(),
_ => unreachable!(),
};
callback(result.expect("empty waiter"));
Ok(())
},
),
)
}
}
23 changes: 22 additions & 1 deletion src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ use windows::{
},
},
};

use crate::{
dpi::{PhysicalPosition, PhysicalSize, PixelUnit},
error::ExternalError,
Expand All @@ -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,
Expand Down Expand Up @@ -144,6 +148,8 @@ pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) dpi_aware: bool,
pub(crate) msg_hook: Option<Box<dyn FnMut(*const c_void) -> bool + 'static>>,
pub(crate) preferred_theme: Option<Theme>,
#[cfg(feature = "push-notifications")]
pub(crate) push_channel: Option<PushNotificationChannel>,
}

impl Default for PlatformSpecificEventLoopAttributes {
Expand All @@ -153,6 +159,8 @@ impl Default for PlatformSpecificEventLoopAttributes {
dpi_aware: true,
msg_hook: None,
preferred_theme: None,
#[cfg(feature = "push-notifications")]
push_channel: None,
}
}
}
Expand All @@ -163,6 +171,8 @@ pub struct EventLoopWindowTarget<T: 'static> {
thread_msg_target: HWND,
pub(crate) preferred_theme: Arc<Mutex<Option<Theme>>>,
pub(crate) runner_shared: EventLoopRunnerShared<T>,
#[cfg(feature = "push-notifications")]
pub(crate) push_channel: Arc<Mutex<Option<PushNotificationChannel>>>,
}

impl<T: 'static> EventLoop<T> {
Expand Down Expand Up @@ -203,6 +213,8 @@ impl<T: 'static> EventLoop<T> {
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,
},
Expand Down Expand Up @@ -338,6 +350,15 @@ impl<T> EventLoopWindowTarget<T> {
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<PushNotificationChannel>) {
*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 {
Expand Down
4 changes: 4 additions & 0 deletions src/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@
/// The push token type.
pub type PushToken = Vec<u8>;

/// 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 {}

0 comments on commit f167515

Please sign in to comment.