Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
use crate::notification::NotificationSendError;
|
||||
use crate::windowing::winit::app::CustomEvent;
|
||||
use crate::windowing::winit::notifications::NotificationInfo;
|
||||
use crate::WindowId;
|
||||
use futures::FutureExt;
|
||||
use winit::event_loop::EventLoopProxy;
|
||||
|
||||
pub(super) async fn send_notification(
|
||||
notification_info: NotificationInfo,
|
||||
_window_id: WindowId,
|
||||
proxy: EventLoopProxy<CustomEvent>,
|
||||
) {
|
||||
let NotificationInfo {
|
||||
notification_content,
|
||||
on_error,
|
||||
} = notification_info;
|
||||
|
||||
let mut notification = notify_rust::Notification::new();
|
||||
notification
|
||||
.summary(notification_content.title())
|
||||
.body(notification_content.body());
|
||||
|
||||
notification
|
||||
.show_async()
|
||||
.then(|handle| async move {
|
||||
match handle {
|
||||
Ok(handle) => {
|
||||
// The call to on_close blocks until the notification is closed, so make the blocking
|
||||
// call on its own thread in the `blocking` crate threadpool to avoid starving the shared
|
||||
// background executor.
|
||||
blocking::unblock(move || {
|
||||
// Without the on_close handler, the notification will fail to appear.
|
||||
handle.on_close(|reason| log::info!("Notification closed via {reason:?}"))
|
||||
})
|
||||
.await;
|
||||
}
|
||||
Err(err) => {
|
||||
// Always consider the error to be a `NotificationSendError::Other`.
|
||||
// Dbus does not report if a notification couldn't be shown because
|
||||
// the application didn't have permissions, so we can never return a
|
||||
// `NotificationSendError::PermissionDenied` error.
|
||||
let error = NotificationSendError::Other {
|
||||
error_message: err.to_string(),
|
||||
};
|
||||
|
||||
let _ = proxy.send_event(CustomEvent::UpdateUIApp(Box::new(|ctx| {
|
||||
on_error(error, ctx);
|
||||
})));
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//! Module to display system desktop notifications through the winit windowing backend.
|
||||
|
||||
use crate::platform::NotificationInfo;
|
||||
use crate::platform::{RequestNotificationPermissionsCallback, SendNotificationErrorCallback};
|
||||
use crate::windowing::winit::app::CustomEvent;
|
||||
use crate::{notification, WindowId};
|
||||
use winit::event_loop::EventLoopProxy;
|
||||
|
||||
#[cfg_attr(target_os = "linux", path = "linux.rs")]
|
||||
#[cfg_attr(target_os = "windows", path = "windows.rs")]
|
||||
#[cfg_attr(target_family = "wasm", path = "wasm.rs")]
|
||||
mod imp;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub(super) use imp::request_notification_permissions;
|
||||
|
||||
pub async fn send_notification(
|
||||
notification_info: NotificationInfo,
|
||||
window_id: WindowId,
|
||||
event_loop_proxy: EventLoopProxy<CustomEvent>,
|
||||
) {
|
||||
imp::send_notification(notification_info, window_id, event_loop_proxy).await
|
||||
}
|
||||
|
||||
pub(super) fn request_desktop_notification_permissions(
|
||||
on_completion: RequestNotificationPermissionsCallback,
|
||||
event_loop_proxy: &EventLoopProxy<CustomEvent>,
|
||||
) {
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::RequestNotificationPermissions(Box::new(
|
||||
|outcome, ctx| on_completion(outcome, ctx),
|
||||
)));
|
||||
}
|
||||
|
||||
pub(super) fn send_desktop_notification(
|
||||
notification_content: notification::UserNotification,
|
||||
window_id: WindowId,
|
||||
on_error: SendNotificationErrorCallback,
|
||||
event_loop_proxy: &EventLoopProxy<CustomEvent>,
|
||||
) {
|
||||
use crate::platform::NotificationInfo;
|
||||
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::SendNotification {
|
||||
window_id,
|
||||
notification_info: NotificationInfo {
|
||||
notification_content,
|
||||
on_error,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
use crate::notification::NotificationSendError;
|
||||
use crate::notification::RequestPermissionsOutcome;
|
||||
use crate::platform::NotificationInfo;
|
||||
use crate::windowing::winit::app::RequestPermissionsCallback;
|
||||
use crate::windowing::winit::CustomEvent;
|
||||
use crate::WindowId;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use winit::event_loop::EventLoopProxy;
|
||||
|
||||
pub async fn send_notification(
|
||||
notification_info: NotificationInfo,
|
||||
_window_id: WindowId,
|
||||
proxy: EventLoopProxy<CustomEvent>,
|
||||
) {
|
||||
let NotificationInfo {
|
||||
notification_content,
|
||||
on_error,
|
||||
} = notification_info;
|
||||
|
||||
// First, we check to see if the page has permissions to send notifications. If not, we should prematurely
|
||||
// execute the on_error callback. We can't rely on the result of the web_sys::Notification constructor to
|
||||
// know whether the notification has the right permissions to actually send.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification#return_value.
|
||||
match web_sys::Notification::permission() {
|
||||
web_sys::NotificationPermission::Granted => {
|
||||
// If permissions are granted, send it! Constructing the Notification object is enough to launch it.
|
||||
let _ = web_sys::Notification::new(notification_content.title());
|
||||
}
|
||||
web_sys::NotificationPermission::Default => {
|
||||
let _ = proxy.send_event(CustomEvent::UpdateUIApp(Box::new(|ctx| {
|
||||
on_error(NotificationSendError::PermissionsNotYetGranted, ctx)
|
||||
})));
|
||||
}
|
||||
web_sys::NotificationPermission::Denied => {
|
||||
let _ = proxy.send_event(CustomEvent::UpdateUIApp(Box::new(|ctx| {
|
||||
on_error(NotificationSendError::PermissionsDenied, ctx)
|
||||
})));
|
||||
}
|
||||
_ => {
|
||||
let _ = proxy.send_event(CustomEvent::UpdateUIApp(Box::new(|ctx| {
|
||||
on_error(
|
||||
NotificationSendError::Other {
|
||||
error_message: "unknown notifications permissions".to_string(),
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn request_notification_permissions(
|
||||
callback: RequestPermissionsCallback,
|
||||
proxy: EventLoopProxy<CustomEvent>,
|
||||
) {
|
||||
// The web_sys request_permission method returns a Promise that resolves to a string indicating
|
||||
// whether the permissions request was granted, denied, or default.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission_static.
|
||||
let Ok(permissions_request_promise) = web_sys::Notification::request_permission() else {
|
||||
let _ = proxy.send_event(CustomEvent::UpdateUIApp(Box::new(|ctx| {
|
||||
callback(
|
||||
RequestPermissionsOutcome::OtherError {
|
||||
error_message: "Error sending notification permissions request".to_string(),
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
})));
|
||||
return;
|
||||
};
|
||||
|
||||
let request_outcome = match JsFuture::from(permissions_request_promise)
|
||||
.await
|
||||
.map(|r| r.as_string())
|
||||
{
|
||||
Ok(Some(user_response)) if user_response == "granted" => {
|
||||
RequestPermissionsOutcome::Accepted
|
||||
}
|
||||
// Any response besides "granted" is considered a permissions denied.
|
||||
Ok(Some(_)) => RequestPermissionsOutcome::PermissionsDenied,
|
||||
_ => RequestPermissionsOutcome::OtherError {
|
||||
error_message: "Error receiving response from notification permissions request"
|
||||
.to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
// When the request has completed, we execute the callback with the outcome.
|
||||
let _ = proxy.send_event(CustomEvent::UpdateUIApp(Box::new(|ctx| {
|
||||
callback(request_outcome, ctx);
|
||||
})));
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use crate::notification::NotificationSendError;
|
||||
use crate::windowing::winit::{app::CustomEvent, notifications::NotificationInfo};
|
||||
use crate::WindowId;
|
||||
use tauri_winrt_notification::Toast;
|
||||
use winit::event_loop::EventLoopProxy;
|
||||
|
||||
pub(super) async fn send_notification(
|
||||
notification_info: NotificationInfo,
|
||||
window_id: WindowId,
|
||||
proxy: EventLoopProxy<CustomEvent>,
|
||||
) {
|
||||
let NotificationInfo {
|
||||
notification_content,
|
||||
on_error,
|
||||
} = notification_info;
|
||||
|
||||
let powershell_app_id = Toast::POWERSHELL_APP_ID.to_string();
|
||||
let app_id = unsafe { fetch_windows_app_id() }
|
||||
.ok()
|
||||
.unwrap_or(powershell_app_id);
|
||||
let proxy_clone = proxy.clone();
|
||||
let toast = Toast::new(&app_id)
|
||||
.title(notification_content.title())
|
||||
.text1(notification_content.body())
|
||||
.on_activated(move |_activated_arguments| {
|
||||
let _ = proxy_clone
|
||||
.send_event(CustomEvent::FocusWindow { window_id })
|
||||
.map_err(|err| {
|
||||
log::warn!("Unable to focus window after event loop closed: {err:?}");
|
||||
});
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if let Err(err) = toast.show() {
|
||||
let error = NotificationSendError::Other {
|
||||
error_message: err.to_string(),
|
||||
};
|
||||
|
||||
let _ = proxy.send_event(CustomEvent::UpdateUIApp(Box::new(|ctx| {
|
||||
on_error(error, ctx);
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn fetch_windows_app_id() -> Result<String, anyhow::Error> {
|
||||
let app_id_pwstr = windows::Win32::UI::Shell::GetCurrentProcessExplicitAppUserModelID()
|
||||
.map_err(|win_err| {
|
||||
log::warn!("error retrieving Win32 AppUserModel ID: {win_err:?}");
|
||||
anyhow::anyhow!(win_err)
|
||||
})?;
|
||||
Ok(app_id_pwstr.to_string()?)
|
||||
}
|
||||
Reference in New Issue
Block a user