use crate::{request_registry::RequestRegistry, tasks::RuntimeWake};
use incular_platform::{
ApplicationShellError, ApplicationShellFeature, CapabilitySupport, NotificationActionId,
NotificationId, NotificationPresentation, PlatformCapabilities, TaskbarDockState, TrayItemId,
TrayItemPresentation,
};
use incular_widgets::internal::PlatformMenuCommandModel;
use incular_widgets::{
PlatformMenu, PlatformMenuBuildError, PlatformMenuEvent, PlatformMenuSnapshot,
};
use std::{
cell::RefCell,
collections::VecDeque,
rc::Rc,
sync::{Arc, Mutex, RwLock},
};
#[derive(Clone, Debug)]
pub enum NativeApplicationShellOperation {
CreateTray {
id: TrayItemId,
presentation: TrayItemPresentation,
menu: PlatformMenuSnapshot,
},
UpdateTray {
id: TrayItemId,
presentation: TrayItemPresentation,
menu: PlatformMenuSnapshot,
},
RemoveTray {
id: TrayItemId,
},
ShowNotification {
id: NotificationId,
presentation: NotificationPresentation,
},
UpdateNotification {
id: NotificationId,
presentation: NotificationPresentation,
},
CloseNotification {
id: NotificationId,
},
SetTaskbarDockState(TaskbarDockState),
}
impl NativeApplicationShellOperation {
#[must_use]
pub fn required_feature(&self) -> ApplicationShellFeature {
match self {
Self::CreateTray { .. } | Self::UpdateTray { .. } | Self::RemoveTray { .. } => {
ApplicationShellFeature::TrayOrStatusItem
}
Self::ShowNotification { .. } => ApplicationShellFeature::Notifications,
Self::UpdateNotification { .. } => ApplicationShellFeature::NotificationUpdate,
Self::CloseNotification { .. } => ApplicationShellFeature::NotificationDismiss,
Self::SetTaskbarDockState(state) => state.required_feature(),
}
}
}
#[derive(Clone, Debug)]
pub struct NativeApplicationShellRequest {
pub request_id: ApplicationShellRequestId,
pub operation: NativeApplicationShellOperation,
}
#[derive(Clone, Debug)]
pub struct NativeApplicationShellCompletion {
pub request_id: ApplicationShellRequestId,
pub result: Result<(), ApplicationShellError>,
}
#[derive(Clone, Debug)]
pub enum NativeApplicationShellApplyResult {
Completed(Result<(), ApplicationShellError>),
Deferred,
}
impl From<Result<(), ApplicationShellError>> for NativeApplicationShellApplyResult {
fn from(result: Result<(), ApplicationShellError>) -> Self {
Self::Completed(result)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ApplicationShellRequestId(u64);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum NativeApplicationShellEvent {
TrayMenu {
id: TrayItemId,
event: PlatformMenuEvent,
},
TrayActivated {
id: TrayItemId,
},
NotificationActivated {
id: NotificationId,
},
NotificationAction {
id: NotificationId,
action: NotificationActionId,
},
NotificationDismissed {
id: NotificationId,
},
}
struct Slot<T> {
generation: u32,
retired: bool,
value: Option<T>,
}
impl<T> Default for Slot<T> {
fn default() -> Self {
Self {
generation: 0,
retired: false,
value: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ResourcePhase {
Queued,
Dispatched,
Live,
Failed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ShellResource {
Tray(TrayItemId),
Notification(NotificationId),
}
struct TrayRecord {
phase: ResourcePhase,
presentation: TrayItemPresentation,
menu: PlatformMenuCommandModel,
on_activated: Option<Arc<dyn Fn() + Send + Sync>>,
}
struct NotificationRecord {
phase: ResourcePhase,
presentation: NotificationPresentation,
on_activated: Option<Arc<dyn Fn() + Send + Sync>>,
on_action: Option<Arc<dyn Fn(NotificationActionId) + Send + Sync>>,
on_dismissed: Option<Arc<dyn Fn() + Send + Sync>>,
}
#[derive(Default)]
struct ShellState {
trays: Vec<Slot<TrayRecord>>,
notifications: Vec<Slot<NotificationRecord>>,
requests: VecDeque<NativeApplicationShellRequest>,
pending: RequestRegistry<ApplicationShellRequestId, NativeApplicationShellOperation>,
completions: VecDeque<NativeApplicationShellCompletion>,
next_request: u64,
stopped: bool,
notification_identity: Option<String>,
}
#[derive(Clone)]
pub struct ApplicationShellService {
state: Rc<RefCell<ShellState>>,
capabilities: Arc<RwLock<PlatformCapabilities>>,
wake: Rc<RefCell<Option<Arc<dyn RuntimeWake>>>>,
}
impl ApplicationShellService {
pub(crate) fn new(capabilities: Arc<RwLock<PlatformCapabilities>>) -> Self {
Self {
state: Rc::new(RefCell::new(ShellState::default())),
capabilities,
wake: Rc::new(RefCell::new(None)),
}
}
pub(crate) fn set_wake(&self, wake: Arc<dyn RuntimeWake>) {
*self.wake.borrow_mut() = Some(wake);
}
fn wake(&self) {
let wake = self.wake.borrow().clone();
if let Some(wake) = wake {
wake.wake();
}
}
#[doc(hidden)]
#[must_use]
pub fn new_for_test(capabilities: Arc<RwLock<PlatformCapabilities>>) -> Self {
Self::new(capabilities)
}
#[must_use]
pub fn capabilities(&self) -> incular_platform::ApplicationServiceCapabilities {
self.capabilities
.read()
.expect("platform capabilities lock")
.application_services
}
pub fn create_tray_item(
&self,
presentation: TrayItemPresentation,
menus: &[PlatformMenu],
) -> Result<TrayItemHandle, ApplicationShellError> {
self.require_support(
self.capabilities().tray_or_status_item,
ApplicationShellFeature::TrayOrStatusItem,
)?;
let menu = PlatformMenuCommandModel::compile(menus).map_err(menu_build_error)?;
let mut state = self.state.borrow_mut();
ensure_running(&state)?;
let id = reserve_tray(
&mut state.trays,
TrayRecord {
phase: ResourcePhase::Queued,
presentation: presentation.clone(),
menu: menu.clone(),
on_activated: None,
},
);
enqueue(
&mut state,
NativeApplicationShellOperation::CreateTray {
id,
presentation,
menu: menu.snapshot().clone(),
},
);
self.wake();
Ok(TrayItemHandle {
id,
service: self.clone(),
released: false,
})
}
pub fn show_notification(
&self,
presentation: NotificationPresentation,
) -> Result<NotificationHandle, ApplicationShellError> {
let capabilities = self.capabilities();
self.require_support(
capabilities.notifications,
ApplicationShellFeature::Notifications,
)?;
if !presentation.actions.is_empty() {
self.require_support(
capabilities.notification_actions,
ApplicationShellFeature::NotificationActions,
)?;
}
let mut state = self.state.borrow_mut();
ensure_running(&state)?;
let id = reserve_notification(
&mut state.notifications,
NotificationRecord {
phase: ResourcePhase::Queued,
presentation: presentation.clone(),
on_activated: None,
on_action: None,
on_dismissed: None,
},
);
enqueue(
&mut state,
NativeApplicationShellOperation::ShowNotification { id, presentation },
);
self.wake();
Ok(NotificationHandle {
id,
service: self.clone(),
released: false,
})
}
pub fn set_notification_identity(
&self,
identity: impl Into<String>,
) -> Result<(), ApplicationShellError> {
let identity = identity.into();
if identity.trim().is_empty() || identity.chars().any(char::is_control) {
return Err(ApplicationShellError::PlatformConfigurationRequired(
"notification identity must be a non-empty printable installed application identity".to_owned(),
));
}
let mut state = self.state.borrow_mut();
ensure_running(&state)?;
state.notification_identity = Some(identity);
Ok(())
}
#[doc(hidden)]
#[must_use]
pub fn notification_identity(&self) -> Option<String> {
self.state.borrow().notification_identity.clone()
}
pub fn set_taskbar_dock_state(
&self,
state_value: TaskbarDockState,
) -> Result<(), ApplicationShellError> {
let capabilities = self.capabilities();
if state_value.progress != incular_platform::TaskbarProgress::None {
self.require_support(
capabilities.taskbar_progress,
ApplicationShellFeature::TaskbarProgress,
)?;
}
if state_value.badge != incular_platform::ApplicationBadge::None {
self.require_support(
capabilities.application_badge,
ApplicationShellFeature::ApplicationBadge,
)?;
}
if state_value.overlay_icon.is_some() {
self.require_support(
capabilities.taskbar_overlay_icon,
ApplicationShellFeature::TaskbarOverlayIcon,
)?;
}
let mut state = self.state.borrow_mut();
ensure_running(&state)?;
enqueue(
&mut state,
NativeApplicationShellOperation::SetTaskbarDockState(state_value),
);
self.wake();
Ok(())
}
fn require_support(
&self,
support: CapabilitySupport,
feature: ApplicationShellFeature,
) -> Result<(), ApplicationShellError> {
if support == CapabilitySupport::Unsupported {
Err(ApplicationShellError::Unsupported(feature))
} else {
Ok(())
}
}
pub(crate) fn take_native_requests(&self) -> Vec<NativeApplicationShellRequest> {
let mut state = self.state.borrow_mut();
let requests: Vec<_> = state.requests.drain(..).collect();
for request in &requests {
state.pending.dispatch(&request.request_id);
if let Some(resource) = created_resource(&request.operation) {
set_resource_phase(&mut state, resource, ResourcePhase::Dispatched);
}
}
requests
}
#[doc(hidden)]
pub fn take_native_requests_for_test(&self) -> Vec<NativeApplicationShellRequest> {
self.take_native_requests()
}
pub(crate) fn complete(&self, completion: NativeApplicationShellCompletion) {
let mut state = self.state.borrow_mut();
if let Ok(operation) = state.pending.complete(&completion.request_id, |_| true) {
if let Some(resource) = created_resource(&operation) {
let phase = if completion.result.is_ok() {
ResourcePhase::Live
} else {
ResourcePhase::Failed
};
set_resource_phase(&mut state, resource, phase);
if phase == ResourcePhase::Failed {
cancel_queued_resource(&mut state, resource);
}
}
state.completions.push_back(completion);
}
}
#[doc(hidden)]
pub fn complete_for_test(&self, completion: NativeApplicationShellCompletion) {
self.complete(completion);
}
#[must_use]
pub fn take_completions(&self) -> Vec<NativeApplicationShellCompletion> {
self.state.borrow_mut().completions.drain(..).collect()
}
pub(crate) fn handle_native_event(&self, event: NativeApplicationShellEvent) -> bool {
match event {
NativeApplicationShellEvent::TrayMenu { id, event } => {
let callback = {
let state = self.state.borrow_mut();
tray_record(&state.trays, id).map(|record| record.menu.clone())
};
callback.is_some_and(|menu| {
matches!(
menu.dispatch(&event),
incular_widgets::MenuDispatchResult::Handled
)
})
}
NativeApplicationShellEvent::TrayActivated { id } => {
let callback = {
let state = self.state.borrow();
tray_record(&state.trays, id).and_then(|record| record.on_activated.clone())
};
callback.is_some_and(|callback| {
callback();
true
})
}
NativeApplicationShellEvent::NotificationActivated { id } => {
notification_callback(&self.state, id, |record| record.on_activated.clone())
}
NativeApplicationShellEvent::NotificationAction { id, action } => {
let callback = {
let state = self.state.borrow_mut();
notification_record(&state.notifications, id).and_then(|record| {
record
.presentation
.actions
.iter()
.any(|known| known.id == action)
.then(|| record.on_action.clone())
.flatten()
})
};
callback.is_some_and(|callback| {
callback(action);
true
})
}
NativeApplicationShellEvent::NotificationDismissed { id } => {
let handled =
notification_callback(&self.state, id, |record| record.on_dismissed.clone());
let dismiss_native =
self.capabilities().notification_dismiss != CapabilitySupport::Unsupported;
let mut state = self.state.borrow_mut();
let phase =
notification_record(&state.notifications, id).map(|record| record.phase);
let released = release_notification(&mut state.notifications, id);
if let Some(phase) = phase {
retire_resource(
&mut state,
ShellResource::Notification(id),
phase,
dismiss_native,
);
}
drop(state);
if released && dismiss_native {
self.wake();
}
handled || released
}
}
}
#[doc(hidden)]
pub fn handle_native_event_for_test(&self, event: NativeApplicationShellEvent) -> bool {
self.handle_native_event(event)
}
pub(crate) fn stop(&self) {
let mut state = self.state.borrow_mut();
state.stopped = true;
state.requests.clear();
let pending: Vec<_> = state
.pending
.drain()
.map(|(request_id, _)| NativeApplicationShellCompletion {
request_id,
result: Err(ApplicationShellError::ApplicationStopped),
})
.collect();
state.completions.extend(pending);
for slot in &mut state.trays {
retire_slot(slot);
}
for slot in &mut state.notifications {
retire_slot(slot);
}
}
}
fn notification_callback<T>(
state: &Rc<RefCell<ShellState>>,
id: NotificationId,
select: impl FnOnce(&NotificationRecord) -> Option<Arc<T>>,
) -> bool
where
T: Fn() + Send + Sync + ?Sized,
{
let callback = {
let state = state.borrow();
notification_record(&state.notifications, id).and_then(select)
};
callback.is_some_and(|callback| {
callback();
true
})
}
fn operation_resource(operation: &NativeApplicationShellOperation) -> Option<ShellResource> {
match operation {
NativeApplicationShellOperation::CreateTray { id, .. }
| NativeApplicationShellOperation::UpdateTray { id, .. }
| NativeApplicationShellOperation::RemoveTray { id } => Some(ShellResource::Tray(*id)),
NativeApplicationShellOperation::ShowNotification { id, .. }
| NativeApplicationShellOperation::UpdateNotification { id, .. }
| NativeApplicationShellOperation::CloseNotification { id } => {
Some(ShellResource::Notification(*id))
}
NativeApplicationShellOperation::SetTaskbarDockState(_) => None,
}
}
fn created_resource(operation: &NativeApplicationShellOperation) -> Option<ShellResource> {
match operation {
NativeApplicationShellOperation::CreateTray { .. }
| NativeApplicationShellOperation::ShowNotification { .. } => operation_resource(operation),
_ => None,
}
}
fn set_resource_phase(state: &mut ShellState, resource: ShellResource, phase: ResourcePhase) {
match resource {
ShellResource::Tray(id) => {
if let Some(record) = tray_record_mut(&mut state.trays, id) {
record.phase = phase;
}
}
ShellResource::Notification(id) => {
if let Some(record) = notification_record_mut(&mut state.notifications, id) {
record.phase = phase;
}
}
}
}
fn cancel_queued_resource(state: &mut ShellState, resource: ShellResource) {
let ids: Vec<_> = state
.requests
.iter()
.filter(|request| operation_resource(&request.operation) == Some(resource))
.map(|request| request.request_id)
.collect();
state
.requests
.retain(|request| operation_resource(&request.operation) != Some(resource));
for request_id in ids {
state.pending.remove(&request_id);
state
.completions
.push_back(NativeApplicationShellCompletion {
request_id,
result: Err(ApplicationShellError::StaleResource),
});
}
}
fn retire_resource(
state: &mut ShellState,
resource: ShellResource,
phase: ResourcePhase,
native_cleanup: bool,
) {
cancel_queued_resource(state, resource);
if native_cleanup && matches!(phase, ResourcePhase::Dispatched | ResourcePhase::Live) {
enqueue(
state,
match resource {
ShellResource::Tray(id) => NativeApplicationShellOperation::RemoveTray { id },
ShellResource::Notification(id) => {
NativeApplicationShellOperation::CloseNotification { id }
}
},
);
}
}
fn menu_build_error(error: PlatformMenuBuildError) -> ApplicationShellError {
ApplicationShellError::NativeFailure(error.to_string())
}
fn ensure_running(state: &ShellState) -> Result<(), ApplicationShellError> {
(!state.stopped)
.then_some(())
.ok_or(ApplicationShellError::ApplicationStopped)
}
fn enqueue(state: &mut ShellState, operation: NativeApplicationShellOperation) {
let request_id = ApplicationShellRequestId(state.next_request);
state.next_request = state
.next_request
.checked_add(1)
.expect("shell request identity exhausted");
state.pending.insert(request_id, operation.clone());
state.requests.push_back(NativeApplicationShellRequest {
request_id,
operation,
});
}
fn reserve_tray(slots: &mut Vec<Slot<TrayRecord>>, value: TrayRecord) -> TrayItemId {
if let Some((index, slot)) = slots
.iter_mut()
.enumerate()
.find(|(_, slot)| !slot.retired && slot.value.is_none())
{
slot.value = Some(value);
return TrayItemId::from_parts(
u32::try_from(index).expect("tray index was validated on insertion"),
slot.generation,
);
}
let index = u32::try_from(slots.len()).expect("tray identity space exhausted");
slots.push(Slot {
generation: 0,
retired: false,
value: Some(value),
});
TrayItemId::from_parts(index, 0)
}
fn reserve_notification(
slots: &mut Vec<Slot<NotificationRecord>>,
value: NotificationRecord,
) -> NotificationId {
if let Some((index, slot)) = slots
.iter_mut()
.enumerate()
.find(|(_, slot)| !slot.retired && slot.value.is_none())
{
slot.value = Some(value);
return NotificationId::from_parts(
u32::try_from(index).expect("notification index was validated on insertion"),
slot.generation,
);
}
let index = u32::try_from(slots.len()).expect("notification identity space exhausted");
slots.push(Slot {
generation: 0,
retired: false,
value: Some(value),
});
NotificationId::from_parts(index, 0)
}
fn tray_record(slots: &[Slot<TrayRecord>], id: TrayItemId) -> Option<&TrayRecord> {
slots
.get(id.index() as usize)
.filter(|slot| slot.generation == id.generation())?
.value
.as_ref()
}
fn tray_record_mut(slots: &mut [Slot<TrayRecord>], id: TrayItemId) -> Option<&mut TrayRecord> {
slots
.get_mut(id.index() as usize)
.filter(|slot| slot.generation == id.generation())?
.value
.as_mut()
}
fn notification_record(
slots: &[Slot<NotificationRecord>],
id: NotificationId,
) -> Option<&NotificationRecord> {
slots
.get(id.index() as usize)
.filter(|slot| slot.generation == id.generation())?
.value
.as_ref()
}
fn notification_record_mut(
slots: &mut [Slot<NotificationRecord>],
id: NotificationId,
) -> Option<&mut NotificationRecord> {
slots
.get_mut(id.index() as usize)
.filter(|slot| slot.generation == id.generation())?
.value
.as_mut()
}
fn release_notification(slots: &mut [Slot<NotificationRecord>], id: NotificationId) -> bool {
let Some(slot) = slots
.get_mut(id.index() as usize)
.filter(|slot| slot.generation == id.generation())
else {
return false;
};
if slot.value.is_none() {
return false;
}
retire_slot(slot);
true
}
fn retire_slot<T>(slot: &mut Slot<T>) {
if slot.value.take().is_none() {
return;
}
if let Some(next_generation) = slot.generation.checked_add(1) {
slot.generation = next_generation;
} else {
slot.retired = true;
}
}
pub struct TrayItemHandle {
id: TrayItemId,
service: ApplicationShellService,
released: bool,
}
impl TrayItemHandle {
#[must_use]
pub const fn id(&self) -> TrayItemId {
self.id
}
pub fn update(
&self,
presentation: TrayItemPresentation,
menus: &[PlatformMenu],
) -> Result<(), ApplicationShellError> {
self.service.require_support(
self.service.capabilities().tray_or_status_item,
ApplicationShellFeature::TrayOrStatusItem,
)?;
let menu = PlatformMenuCommandModel::compile(menus).map_err(menu_build_error)?;
let mut state = self.service.state.borrow_mut();
ensure_running(&state)?;
let record = tray_record_mut(&mut state.trays, self.id)
.ok_or(ApplicationShellError::StaleResource)?;
if record.phase == ResourcePhase::Failed {
return Err(ApplicationShellError::StaleResource);
}
record.presentation = presentation.clone();
record.menu = menu.clone();
enqueue(
&mut state,
NativeApplicationShellOperation::UpdateTray {
id: self.id,
presentation,
menu: menu.snapshot().clone(),
},
);
drop(state);
self.service.wake();
Ok(())
}
pub fn on_activated(
&self,
callback: impl Fn() + Send + Sync + 'static,
) -> Result<(), ApplicationShellError> {
let mut state = self.service.state.borrow_mut();
ensure_running(&state)?;
let record = tray_record_mut(&mut state.trays, self.id)
.ok_or(ApplicationShellError::StaleResource)?;
record.on_activated = Some(Arc::new(callback));
Ok(())
}
pub fn remove(mut self) -> Result<(), ApplicationShellError> {
self.service.remove_tray(self.id)?;
self.released = true;
Ok(())
}
}
impl Drop for TrayItemHandle {
fn drop(&mut self) {
if !self.released {
let _ = self.service.remove_tray(self.id);
}
}
}
impl ApplicationShellService {
fn remove_tray(&self, id: TrayItemId) -> Result<(), ApplicationShellError> {
let mut state = self.state.borrow_mut();
ensure_running(&state)?;
let slot = state
.trays
.get_mut(id.index() as usize)
.filter(|slot| slot.generation == id.generation())
.ok_or(ApplicationShellError::StaleResource)?;
let record = slot
.value
.take()
.ok_or(ApplicationShellError::StaleResource)?;
if let Some(next_generation) = slot.generation.checked_add(1) {
slot.generation = next_generation;
} else {
slot.retired = true;
}
retire_resource(&mut state, ShellResource::Tray(id), record.phase, true);
drop(state);
self.wake();
Ok(())
}
}
pub struct NotificationHandle {
id: NotificationId,
service: ApplicationShellService,
released: bool,
}
impl NotificationHandle {
#[must_use]
pub const fn id(&self) -> NotificationId {
self.id
}
pub fn on_activated(
&self,
callback: impl Fn() + Send + Sync + 'static,
) -> Result<(), ApplicationShellError> {
self.with_record(|record| record.on_activated = Some(Arc::new(callback)))
}
pub fn on_action(
&self,
callback: impl Fn(NotificationActionId) + Send + Sync + 'static,
) -> Result<(), ApplicationShellError> {
self.with_record(|record| record.on_action = Some(Arc::new(callback)))
}
pub fn on_dismissed(
&self,
callback: impl Fn() + Send + Sync + 'static,
) -> Result<(), ApplicationShellError> {
self.with_record(|record| record.on_dismissed = Some(Arc::new(callback)))
}
pub fn update(
&self,
presentation: NotificationPresentation,
) -> Result<(), ApplicationShellError> {
self.service.require_support(
self.service.capabilities().notification_update,
ApplicationShellFeature::NotificationUpdate,
)?;
if !presentation.actions.is_empty() {
self.service.require_support(
self.service.capabilities().notification_actions,
ApplicationShellFeature::NotificationActions,
)?;
}
let mut state = self.service.state.borrow_mut();
ensure_running(&state)?;
let record = notification_record_mut(&mut state.notifications, self.id)
.ok_or(ApplicationShellError::StaleResource)?;
if record.phase == ResourcePhase::Failed {
return Err(ApplicationShellError::StaleResource);
}
record.presentation = presentation.clone();
enqueue(
&mut state,
NativeApplicationShellOperation::UpdateNotification {
id: self.id,
presentation,
},
);
drop(state);
self.service.wake();
Ok(())
}
fn with_record(
&self,
update: impl FnOnce(&mut NotificationRecord),
) -> Result<(), ApplicationShellError> {
let mut state = self.service.state.borrow_mut();
ensure_running(&state)?;
let record = notification_record_mut(&mut state.notifications, self.id)
.ok_or(ApplicationShellError::StaleResource)?;
update(record);
Ok(())
}
pub fn close(mut self) -> Result<(), ApplicationShellError> {
self.service.require_support(
self.service.capabilities().notification_dismiss,
ApplicationShellFeature::NotificationDismiss,
)?;
self.service.release_notification_handle(self.id, true)?;
self.released = true;
Ok(())
}
}
impl Drop for NotificationHandle {
fn drop(&mut self) {
if self.released {
return;
}
let dismiss_native =
self.service.capabilities().notification_dismiss != CapabilitySupport::Unsupported;
let _ = self
.service
.release_notification_handle(self.id, dismiss_native);
}
}
impl ApplicationShellService {
fn release_notification_handle(
&self,
id: NotificationId,
dismiss_native: bool,
) -> Result<(), ApplicationShellError> {
let mut state = self.state.borrow_mut();
ensure_running(&state)?;
let phase = notification_record(&state.notifications, id)
.ok_or(ApplicationShellError::StaleResource)?
.phase;
release_notification(&mut state.notifications, id);
retire_resource(
&mut state,
ShellResource::Notification(id),
phase,
dismiss_native,
);
drop(state);
if dismiss_native {
self.wake();
}
Ok(())
}
}
#[derive(Clone, Default)]
pub struct MemoryApplicationShellAdapter {
operations: Arc<Mutex<Vec<NativeApplicationShellOperation>>>,
}
impl MemoryApplicationShellAdapter {
pub fn apply(
&self,
request: NativeApplicationShellRequest,
) -> NativeApplicationShellCompletion {
self.operations
.lock()
.expect("memory shell adapter lock")
.push(request.operation);
NativeApplicationShellCompletion {
request_id: request.request_id,
result: Ok(()),
}
}
#[must_use]
pub fn operations(&self) -> Vec<NativeApplicationShellOperation> {
self.operations
.lock()
.expect("memory shell adapter lock")
.clone()
}
}