use std::{
collections::HashMap,
fmt,
path::PathBuf,
sync::{self, Arc},
};
pub mod raw_device_events;
pub mod raw_events;
use crate::{
event::{event, event_args},
window::{MonitorId, WindowId},
};
use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock};
use zng_app_context::app_local;
use zng_layout::unit::{DipPoint, DipRect, DipSideOffsets, DipSize, Factor, Px, PxPoint, PxRect, PxSize};
use zng_task::SignalOnce;
use zng_txt::Txt;
use zng_var::ResponderVar;
use zng_view_api::{
self,
api_extension::{ApiExtensionId, ApiExtensionName, ApiExtensionPayload, ApiExtensionRecvError, ApiExtensions},
config::{AnimationsConfig, ChromeConfig, ColorsConfig, FontAntiAliasing, LocaleConfig, MultiClickConfig, TouchConfig},
dialog::{FileDialog, FileDialogResponse, MsgDialog, MsgDialogResponse},
drag_drop::{DragDropData, DragDropEffect, DragDropError},
font::FontOptions,
image::{ImageMaskMode, ImagePpi, ImageRequest, ImageTextureId},
ipc::{IpcBytes, IpcBytesReceiver},
window::{
CursorIcon, FocusIndicator, FrameRequest, FrameUpdateRequest, HeadlessOpenData, HeadlessRequest, MonitorInfo, RenderMode,
ResizeDirection, VideoMode, WindowButton, WindowRequest, WindowStateAll,
},
DragDropId, Event, FocusResult, ViewProcessGen, ViewProcessOffline,
};
use zng_view_api::{
clipboard::{ClipboardData, ClipboardError, ClipboardType},
config::KeyRepeatConfig,
font::{FontFaceId, FontId, FontVariationName},
image::{ImageId, ImageLoadedData},
};
pub(crate) use zng_view_api::{window::MonitorId as ApiMonitorId, window::WindowId as ApiWindowId, Controller, DeviceId as ApiDeviceId};
use self::raw_device_events::DeviceId;
use super::{AppId, APP};
#[expect(non_camel_case_types)]
pub struct VIEW_PROCESS;
struct ViewProcessService {
process: zng_view_api::Controller,
device_ids: HashMap<ApiDeviceId, DeviceId>,
monitor_ids: HashMap<ApiMonitorId, MonitorId>,
data_generation: ViewProcessGen,
extensions: ApiExtensions,
loading_images: Vec<sync::Weak<RwLock<ViewImageData>>>,
frame_images: Vec<sync::Weak<RwLock<ViewImageData>>>,
encoding_images: Vec<EncodeRequest>,
pending_frames: usize,
message_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<MsgDialogResponse>)>,
file_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<FileDialogResponse>)>,
}
app_local! {
static VIEW_PROCESS_SV: Option<ViewProcessService> = None;
}
impl VIEW_PROCESS {
pub fn is_available(&self) -> bool {
APP.is_running() && VIEW_PROCESS_SV.read().is_some()
}
fn read(&self) -> MappedRwLockReadGuard<ViewProcessService> {
VIEW_PROCESS_SV.read_map(|e| e.as_ref().expect("VIEW_PROCESS not available"))
}
fn write(&self) -> MappedRwLockWriteGuard<ViewProcessService> {
VIEW_PROCESS_SV.write_map(|e| e.as_mut().expect("VIEW_PROCESS not available"))
}
fn try_write(&self) -> Result<MappedRwLockWriteGuard<ViewProcessService>> {
let vp = VIEW_PROCESS_SV.write();
if let Some(w) = &*vp {
if w.process.online() {
return Ok(MappedRwLockWriteGuard::map(vp, |w| w.as_mut().unwrap()));
}
}
Err(ViewProcessOffline)
}
fn check_app(&self, id: AppId) {
let actual = APP.id();
if Some(id) != actual {
panic!("cannot use view handle from app `{id:?}` in app `{actual:?}`");
}
}
fn handle_write(&self, id: AppId) -> MappedRwLockWriteGuard<ViewProcessService> {
self.check_app(id);
self.write()
}
pub fn is_online(&self) -> bool {
self.read().process.online()
}
pub fn is_headless_with_render(&self) -> bool {
self.read().process.headless()
}
pub fn is_same_process(&self) -> bool {
self.read().process.same_process()
}
pub fn generation(&self) -> ViewProcessGen {
self.read().process.generation()
}
pub fn open_window(&self, config: WindowRequest) -> Result<()> {
let _s = tracing::debug_span!("VIEW_PROCESS.open_window").entered();
self.write().process.open_window(config)
}
pub fn open_headless(&self, config: HeadlessRequest) -> Result<()> {
let _s = tracing::debug_span!("VIEW_PROCESS.open_headless").entered();
self.write().process.open_headless(config)
}
pub fn add_image(&self, request: ImageRequest<IpcBytes>) -> Result<ViewImage> {
let mut app = self.write();
let id = app.process.add_image(request)?;
let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
id: Some(id),
app_id: APP.id(),
generation: app.process.generation(),
size: PxSize::zero(),
partial_size: PxSize::zero(),
ppi: None,
is_opaque: false,
partial_pixels: None,
pixels: None,
is_mask: false,
done_signal: SignalOnce::new(),
})));
app.loading_images.push(Arc::downgrade(&img.0));
Ok(img)
}
pub fn add_image_pro(&self, request: ImageRequest<IpcBytesReceiver>) -> Result<ViewImage> {
let mut app = self.write();
let id = app.process.add_image_pro(request)?;
let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
id: Some(id),
app_id: APP.id(),
generation: app.process.generation(),
size: PxSize::zero(),
partial_size: PxSize::zero(),
ppi: None,
is_opaque: false,
partial_pixels: None,
pixels: None,
is_mask: false,
done_signal: SignalOnce::new(),
})));
app.loading_images.push(Arc::downgrade(&img.0));
Ok(img)
}
pub fn clipboard(&self) -> Result<&ViewClipboard> {
if VIEW_PROCESS.is_online() {
Ok(&ViewClipboard {})
} else {
Err(ViewProcessOffline)
}
}
pub fn image_decoders(&self) -> Result<Vec<Txt>> {
self.write().process.image_decoders()
}
pub fn image_encoders(&self) -> Result<Vec<Txt>> {
self.write().process.image_encoders()
}
pub fn pending_frames(&self) -> usize {
self.write().pending_frames
}
pub fn respawn(&self) {
self.write().process.respawn()
}
pub fn extension_id(&self, extension_name: impl Into<ApiExtensionName>) -> Result<Option<ApiExtensionId>> {
let me = self.read();
if me.process.online() {
Ok(me.extensions.id(&extension_name.into()))
} else {
Err(ViewProcessOffline)
}
}
pub fn third_party_licenses(&self) -> Result<Vec<crate::third_party::LicenseUsed>> {
self.write().process.third_party_licenses()
}
pub fn app_extension_raw(&self, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
self.write().process.app_extension(extension_id, extension_request)
}
pub fn app_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
where
I: serde::Serialize,
O: serde::de::DeserializeOwned,
{
let payload = ApiExtensionPayload::serialize(&request).unwrap();
let response = self.write().process.app_extension(extension_id, payload)?;
Ok(response.deserialize::<O>())
}
pub fn handle_disconnect(&self, gen: ViewProcessGen) {
self.write().process.handle_disconnect(gen)
}
pub(super) fn start<F>(
&self,
view_process_exe: PathBuf,
view_process_env: HashMap<Txt, Txt>,
device_events: bool,
headless: bool,
on_event: F,
) where
F: FnMut(Event) + Send + 'static,
{
let _s = tracing::debug_span!("VIEW_PROCESS.start").entered();
let process = zng_view_api::Controller::start(view_process_exe, view_process_env, device_events, headless, on_event);
*VIEW_PROCESS_SV.write() = Some(ViewProcessService {
data_generation: process.generation(),
process,
device_ids: HashMap::default(),
monitor_ids: HashMap::default(),
loading_images: vec![],
encoding_images: vec![],
frame_images: vec![],
pending_frames: 0,
message_dialogs: vec![],
file_dialogs: vec![],
extensions: ApiExtensions::default(),
});
}
pub(crate) fn on_window_opened(&self, window_id: WindowId, data: zng_view_api::window::WindowOpenData) -> (ViewWindow, WindowOpenData) {
let mut app = self.write();
let _ = app.check_generation();
let win = ViewWindow(Arc::new(ViewWindowData {
app_id: APP.id().unwrap(),
id: ApiWindowId::from_raw(window_id.get()),
generation: app.data_generation,
}));
drop(app);
let data = WindowOpenData::new(data, |id| self.monitor_id(id));
(win, data)
}
pub(super) fn device_id(&self, id: ApiDeviceId) -> DeviceId {
*self.write().device_ids.entry(id).or_insert_with(DeviceId::new_unique)
}
pub(super) fn monitor_id(&self, id: ApiMonitorId) -> MonitorId {
*self.write().monitor_ids.entry(id).or_insert_with(MonitorId::new_unique)
}
pub(super) fn handle_inited(&self, gen: ViewProcessGen, extensions: ApiExtensions) {
let mut me = self.write();
me.extensions = extensions;
me.process.handle_inited(gen);
}
pub(super) fn handle_suspended(&self) {
self.write().process.handle_suspended();
}
pub(crate) fn on_headless_opened(
&self,
id: WindowId,
data: zng_view_api::window::HeadlessOpenData,
) -> (ViewHeadless, HeadlessOpenData) {
let mut app = self.write();
let _ = app.check_generation();
let surf = ViewHeadless(Arc::new(ViewWindowData {
app_id: APP.id().unwrap(),
id: ApiWindowId::from_raw(id.get()),
generation: app.data_generation,
}));
(surf, data)
}
fn loading_image_index(&self, id: ImageId) -> Option<usize> {
let mut app = self.write();
app.loading_images.retain(|i| i.strong_count() > 0);
app.loading_images.iter().position(|i| i.upgrade().unwrap().read().id == Some(id))
}
pub(super) fn on_image_metadata_loaded(&self, id: ImageId, size: PxSize, ppi: Option<ImagePpi>, is_mask: bool) -> Option<ViewImage> {
if let Some(i) = self.loading_image_index(id) {
let img = self.read().loading_images[i].upgrade().unwrap();
{
let mut img = img.write();
img.size = size;
img.ppi = ppi;
img.is_mask = is_mask;
}
Some(ViewImage(img))
} else {
None
}
}
pub(super) fn on_image_partially_loaded(
&self,
id: ImageId,
partial_size: PxSize,
ppi: Option<ImagePpi>,
is_opaque: bool,
is_mask: bool,
partial_pixels: IpcBytes,
) -> Option<ViewImage> {
if let Some(i) = self.loading_image_index(id) {
let img = self.read().loading_images[i].upgrade().unwrap();
{
let mut img = img.write();
img.partial_size = partial_size;
img.ppi = ppi;
img.is_opaque = is_opaque;
img.partial_pixels = Some(partial_pixels);
img.is_mask = is_mask;
}
Some(ViewImage(img))
} else {
None
}
}
pub(super) fn on_image_loaded(&self, data: ImageLoadedData) -> Option<ViewImage> {
if let Some(i) = self.loading_image_index(data.id) {
let img = self.write().loading_images.swap_remove(i).upgrade().unwrap();
{
let mut img = img.write();
img.size = data.size;
img.partial_size = data.size;
img.ppi = data.ppi;
img.is_opaque = data.is_opaque;
img.pixels = Some(Ok(data.pixels));
img.partial_pixels = None;
img.is_mask = data.is_mask;
img.done_signal.set();
}
Some(ViewImage(img))
} else {
None
}
}
pub(super) fn on_image_error(&self, id: ImageId, error: Txt) -> Option<ViewImage> {
if let Some(i) = self.loading_image_index(id) {
let img = self.write().loading_images.swap_remove(i).upgrade().unwrap();
{
let mut img = img.write();
img.pixels = Some(Err(error));
img.done_signal.set();
}
Some(ViewImage(img))
} else {
None
}
}
pub(crate) fn on_frame_rendered(&self, _id: WindowId) {
let mut vp = self.write();
vp.pending_frames = vp.pending_frames.saturating_sub(1);
}
pub(crate) fn on_frame_image(&self, data: ImageLoadedData) -> ViewImage {
ViewImage(Arc::new(RwLock::new(ViewImageData {
app_id: APP.id(),
id: Some(data.id),
generation: self.generation(),
size: data.size,
partial_size: data.size,
ppi: data.ppi,
is_opaque: data.is_opaque,
partial_pixels: None,
pixels: Some(Ok(data.pixels)),
is_mask: data.is_mask,
done_signal: SignalOnce::new_set(),
})))
}
pub(super) fn on_frame_image_ready(&self, id: ImageId) -> Option<ViewImage> {
let mut app = self.write();
app.frame_images.retain(|i| i.strong_count() > 0);
let i = app.frame_images.iter().position(|i| i.upgrade().unwrap().read().id == Some(id));
i.map(|i| ViewImage(app.frame_images.swap_remove(i).upgrade().unwrap()))
}
pub(super) fn on_image_encoded(&self, id: ImageId, format: Txt, data: IpcBytes) {
self.on_image_encode_result(id, format, Ok(data));
}
pub(super) fn on_image_encode_error(&self, id: ImageId, format: Txt, error: Txt) {
self.on_image_encode_result(id, format, Err(EncodeError::Encode(error)));
}
fn on_image_encode_result(&self, id: ImageId, format: Txt, result: std::result::Result<IpcBytes, EncodeError>) {
let mut app = self.write();
app.encoding_images.retain(move |r| {
let done = r.image_id == id && r.format == format;
if done {
for sender in &r.listeners {
let _ = sender.send(result.clone());
}
}
!done
})
}
pub(crate) fn on_message_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: MsgDialogResponse) {
let mut app = self.write();
if let Some(i) = app.message_dialogs.iter().position(|(i, _)| *i == id) {
let (_, r) = app.message_dialogs.swap_remove(i);
r.respond(response);
}
}
pub(crate) fn on_file_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: FileDialogResponse) {
let mut app = self.write();
if let Some(i) = app.file_dialogs.iter().position(|(i, _)| *i == id) {
let (_, r) = app.file_dialogs.swap_remove(i);
r.respond(response);
}
}
pub(super) fn on_respawned(&self, _gen: ViewProcessGen) {
let mut app = self.write();
app.pending_frames = 0;
for (_, r) in app.message_dialogs.drain(..) {
r.respond(MsgDialogResponse::Error(Txt::from_static("respawn")));
}
}
pub(crate) fn exit(&self) {
*VIEW_PROCESS_SV.write() = None;
}
}
impl ViewProcessService {
#[must_use = "if `true` all current WinId, DevId and MonId are invalid"]
fn check_generation(&mut self) -> bool {
let gen = self.process.generation();
let invalid = gen != self.data_generation;
if invalid {
self.data_generation = gen;
self.device_ids.clear();
self.monitor_ids.clear();
}
invalid
}
}
event_args! {
pub struct ViewProcessInitedArgs {
pub generation: ViewProcessGen,
pub is_respawn: bool,
pub available_monitors: Vec<(MonitorId, MonitorInfo)>,
pub multi_click_config: MultiClickConfig,
pub key_repeat_config: KeyRepeatConfig,
pub touch_config: TouchConfig,
pub font_aa: FontAntiAliasing,
pub animations_config: AnimationsConfig,
pub locale_config: LocaleConfig,
pub colors_config: ColorsConfig,
pub chrome_config: ChromeConfig,
pub extensions: ApiExtensions,
..
fn delivery_list(&self, list: &mut UpdateDeliveryList) {
list.search_all()
}
}
pub struct ViewProcessSuspendedArgs {
..
fn delivery_list(&self, list: &mut UpdateDeliveryList) {
list.search_all()
}
}
}
event! {
pub static VIEW_PROCESS_INITED_EVENT: ViewProcessInitedArgs;
pub static VIEW_PROCESS_SUSPENDED_EVENT: ViewProcessSuspendedArgs;
}
#[derive(Debug, Clone)]
pub struct WindowOpenData {
pub state: WindowStateAll,
pub monitor: Option<MonitorId>,
pub position: (PxPoint, DipPoint),
pub size: DipSize,
pub scale_factor: Factor,
pub render_mode: RenderMode,
pub safe_padding: DipSideOffsets,
}
impl WindowOpenData {
pub(crate) fn new(data: zng_view_api::window::WindowOpenData, map_monitor: impl FnOnce(ApiMonitorId) -> MonitorId) -> Self {
WindowOpenData {
state: data.state,
monitor: data.monitor.map(map_monitor),
position: data.position,
size: data.size,
scale_factor: data.scale_factor,
render_mode: data.render_mode,
safe_padding: data.safe_padding,
}
}
}
#[derive(Debug, Clone)]
#[must_use = "the window is closed when all clones of the handle are dropped"]
pub struct ViewWindow(Arc<ViewWindowData>);
impl PartialEq for ViewWindow {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for ViewWindow {}
impl ViewWindow {
pub fn generation(&self) -> ViewProcessGen {
self.0.generation
}
pub fn set_title(&self, title: Txt) -> Result<()> {
self.0.call(|id, p| p.set_title(id, title))
}
pub fn set_visible(&self, visible: bool) -> Result<()> {
self.0.call(|id, p| p.set_visible(id, visible))
}
pub fn set_always_on_top(&self, always_on_top: bool) -> Result<()> {
self.0.call(|id, p| p.set_always_on_top(id, always_on_top))
}
pub fn set_movable(&self, movable: bool) -> Result<()> {
self.0.call(|id, p| p.set_movable(id, movable))
}
pub fn set_resizable(&self, resizable: bool) -> Result<()> {
self.0.call(|id, p| p.set_resizable(id, resizable))
}
pub fn set_icon(&self, icon: Option<&ViewImage>) -> Result<()> {
self.0.call(|id, p| {
if let Some(icon) = icon {
let icon = icon.0.read();
if p.generation() == icon.generation {
p.set_icon(id, icon.id)
} else {
Err(ViewProcessOffline)
}
} else {
p.set_icon(id, None)
}
})
}
pub fn set_cursor(&self, cursor: Option<CursorIcon>) -> Result<()> {
self.0.call(|id, p| p.set_cursor(id, cursor))
}
pub fn set_cursor_image(&self, cursor: Option<&ViewImage>, hotspot: PxPoint) -> Result<()> {
self.0.call(|id, p| {
if let Some(cur) = cursor {
let cur = cur.0.read();
if p.generation() == cur.generation {
p.set_cursor_image(id, cur.id.map(|img| zng_view_api::window::CursorImage { img, hotspot }))
} else {
Err(ViewProcessOffline)
}
} else {
p.set_cursor_image(id, None)
}
})
}
pub fn set_taskbar_visible(&self, visible: bool) -> Result<()> {
self.0.call(|id, p| p.set_taskbar_visible(id, visible))
}
pub fn bring_to_top(&self) -> Result<()> {
self.0.call(|id, p| p.bring_to_top(id))
}
pub fn set_state(&self, state: WindowStateAll) -> Result<()> {
self.0.call(|id, p| p.set_state(id, state))
}
pub fn set_video_mode(&self, mode: VideoMode) -> Result<()> {
self.0.call(|id, p| p.set_video_mode(id, mode))
}
pub fn set_enabled_buttons(&self, buttons: WindowButton) -> Result<()> {
self.0.call(|id, p| p.set_enabled_buttons(id, buttons))
}
pub fn renderer(&self) -> ViewRenderer {
ViewRenderer(Arc::downgrade(&self.0))
}
pub fn set_capture_mode(&self, enabled: bool) -> Result<()> {
self.0.call(|id, p| p.set_capture_mode(id, enabled))
}
pub fn focus(&self) -> Result<FocusResult> {
self.0.call(|id, p| p.focus(id))
}
pub fn set_focus_indicator(&self, indicator: Option<FocusIndicator>) -> Result<()> {
self.0.call(|id, p| p.set_focus_indicator(id, indicator))
}
pub fn drag_move(&self) -> Result<()> {
self.0.call(|id, p| p.drag_move(id))
}
pub fn drag_resize(&self, direction: ResizeDirection) -> Result<()> {
self.0.call(|id, p| p.drag_resize(id, direction))
}
pub fn start_drag_drop(
&self,
data: Vec<DragDropData>,
allowed_effects: DragDropEffect,
) -> Result<std::result::Result<DragDropId, DragDropError>> {
self.0.call(|id, p| p.start_drag_drop(id, data, allowed_effects))
}
pub fn drag_dropped(&self, drop_id: DragDropId, applied: DragDropEffect) -> Result<()> {
self.0.call(|id, p| p.drag_dropped(id, drop_id, applied))
}
pub fn open_title_bar_context_menu(&self, position: DipPoint) -> Result<()> {
self.0.call(|id, p| p.open_title_bar_context_menu(id, position))
}
pub fn message_dialog(&self, dlg: MsgDialog, responder: ResponderVar<MsgDialogResponse>) -> Result<()> {
let dlg_id = self.0.call(|id, p| p.message_dialog(id, dlg))?;
VIEW_PROCESS.handle_write(self.0.app_id).message_dialogs.push((dlg_id, responder));
Ok(())
}
pub fn file_dialog(&self, dlg: FileDialog, responder: ResponderVar<FileDialogResponse>) -> Result<()> {
let dlg_id = self.0.call(|id, p| p.file_dialog(id, dlg))?;
VIEW_PROCESS.handle_write(self.0.app_id).file_dialogs.push((dlg_id, responder));
Ok(())
}
pub fn access_update(&self, update: zng_view_api::access::AccessTreeUpdate) -> Result<()> {
self.0.call(|id, p| p.access_update(id, update))
}
pub fn set_ime_area(&self, area: Option<DipRect>) -> Result<()> {
self.0.call(|id, p| p.set_ime_area(id, area))
}
pub fn set_system_shutdown_warn(&self, reason: Txt) -> Result<()> {
self.0.call(move |id, p| p.set_system_shutdown_warn(id, reason))
}
pub fn close(self) {
drop(self)
}
pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
self.0.call(|id, p| p.window_extension(id, extension_id, request))
}
pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
where
I: serde::Serialize,
O: serde::de::DeserializeOwned,
{
let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
Ok(r.deserialize())
}
}
#[derive(Clone, Debug)]
pub enum ViewWindowOrHeadless {
Window(ViewWindow),
Headless(ViewHeadless),
}
impl ViewWindowOrHeadless {
pub fn renderer(&self) -> ViewRenderer {
match self {
ViewWindowOrHeadless::Window(w) => w.renderer(),
ViewWindowOrHeadless::Headless(h) => h.renderer(),
}
}
pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
match self {
ViewWindowOrHeadless::Window(w) => w.window_extension_raw(extension_id, request),
ViewWindowOrHeadless::Headless(h) => h.window_extension_raw(extension_id, request),
}
}
pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
where
I: serde::Serialize,
O: serde::de::DeserializeOwned,
{
match self {
ViewWindowOrHeadless::Window(w) => w.window_extension(extension_id, request),
ViewWindowOrHeadless::Headless(h) => h.window_extension(extension_id, request),
}
}
}
impl From<ViewWindow> for ViewWindowOrHeadless {
fn from(w: ViewWindow) -> Self {
ViewWindowOrHeadless::Window(w)
}
}
impl From<ViewHeadless> for ViewWindowOrHeadless {
fn from(w: ViewHeadless) -> Self {
ViewWindowOrHeadless::Headless(w)
}
}
#[derive(Debug)]
struct ViewWindowData {
app_id: AppId,
id: ApiWindowId,
generation: ViewProcessGen,
}
impl ViewWindowData {
fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
let mut app = VIEW_PROCESS.handle_write(self.app_id);
if app.check_generation() {
Err(ViewProcessOffline)
} else {
f(self.id, &mut app.process)
}
}
}
impl Drop for ViewWindowData {
fn drop(&mut self) {
if VIEW_PROCESS.is_available() {
let mut app = VIEW_PROCESS.handle_write(self.app_id);
if self.generation == app.process.generation() {
let _ = app.process.close(self.id);
}
}
}
}
type Result<T> = std::result::Result<T, ViewProcessOffline>;
#[derive(Clone, Debug)]
#[must_use = "the view is disposed when all clones of the handle are dropped"]
pub struct ViewHeadless(Arc<ViewWindowData>);
impl PartialEq for ViewHeadless {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for ViewHeadless {}
impl ViewHeadless {
pub fn set_size(&self, size: DipSize, scale_factor: Factor) -> Result<()> {
self.0.call(|id, p| p.set_headless_size(id, size, scale_factor))
}
pub fn renderer(&self) -> ViewRenderer {
ViewRenderer(Arc::downgrade(&self.0))
}
pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
self.0.call(|id, p| p.window_extension(id, extension_id, request))
}
pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
where
I: serde::Serialize,
O: serde::de::DeserializeOwned,
{
let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
Ok(r.deserialize())
}
}
#[derive(Clone, Debug)]
pub struct ViewRenderer(sync::Weak<ViewWindowData>);
impl PartialEq for ViewRenderer {
fn eq(&self, other: &Self) -> bool {
if let (Some(s), Some(o)) = (self.0.upgrade(), other.0.upgrade()) {
Arc::ptr_eq(&s, &o)
} else {
false
}
}
}
impl Eq for ViewRenderer {}
impl ViewRenderer {
fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
if let Some(c) = self.0.upgrade() {
c.call(f)
} else {
Err(ViewProcessOffline)
}
}
pub fn generation(&self) -> Result<ViewProcessGen> {
self.0.upgrade().map(|c| c.generation).ok_or(ViewProcessOffline)
}
pub fn use_image(&self, image: &ViewImage) -> Result<ImageTextureId> {
self.call(|id, p| {
let image = image.0.read();
if p.generation() == image.generation {
p.use_image(id, image.id.unwrap_or(ImageId::INVALID))
} else {
Err(ViewProcessOffline)
}
})
}
pub fn update_image_use(&mut self, tex_id: ImageTextureId, image: &ViewImage) -> Result<()> {
self.call(|id, p| {
let image = image.0.read();
if p.generation() == image.generation {
p.update_image_use(id, tex_id, image.id.unwrap_or(ImageId::INVALID))
} else {
Err(ViewProcessOffline)
}
})
}
pub fn delete_image_use(&mut self, tex_id: ImageTextureId) -> Result<()> {
self.call(|id, p| p.delete_image_use(id, tex_id))
}
pub fn add_font_face(&self, bytes: Vec<u8>, index: u32) -> Result<FontFaceId> {
self.call(|id, p| p.add_font_face(id, IpcBytes::from_vec(bytes), index))
}
pub fn delete_font_face(&self, font_face_id: FontFaceId) -> Result<()> {
self.call(|id, p| p.delete_font_face(id, font_face_id))
}
pub fn add_font(
&self,
font_face_id: FontFaceId,
glyph_size: Px,
options: FontOptions,
variations: Vec<(FontVariationName, f32)>,
) -> Result<FontId> {
self.call(|id, p| p.add_font(id, font_face_id, glyph_size, options, variations))
}
pub fn delete_font(&self, font_id: FontId) -> Result<()> {
self.call(|id, p| p.delete_font(id, font_id))
}
pub fn frame_image(&self, mask: Option<ImageMaskMode>) -> Result<ViewImage> {
if let Some(c) = self.0.upgrade() {
let id = c.call(|id, p| p.frame_image(id, mask))?;
Ok(Self::add_frame_image(c.app_id, id))
} else {
Err(ViewProcessOffline)
}
}
pub fn frame_image_rect(&self, rect: PxRect, mask: Option<ImageMaskMode>) -> Result<ViewImage> {
if let Some(c) = self.0.upgrade() {
let id = c.call(|id, p| p.frame_image_rect(id, rect, mask))?;
Ok(Self::add_frame_image(c.app_id, id))
} else {
Err(ViewProcessOffline)
}
}
fn add_frame_image(app_id: AppId, id: ImageId) -> ViewImage {
if id == ImageId::INVALID {
ViewImage::dummy(None)
} else {
let mut app = VIEW_PROCESS.handle_write(app_id);
let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
app_id: Some(app_id),
id: Some(id),
generation: app.process.generation(),
size: PxSize::zero(),
partial_size: PxSize::zero(),
ppi: None,
is_opaque: false,
partial_pixels: None,
pixels: None,
is_mask: false,
done_signal: SignalOnce::new(),
})));
app.loading_images.push(Arc::downgrade(&img.0));
app.frame_images.push(Arc::downgrade(&img.0));
img
}
}
pub fn render(&self, frame: FrameRequest) -> Result<()> {
let _s = tracing::debug_span!("ViewRenderer.render").entered();
if let Some(w) = self.0.upgrade() {
w.call(|id, p| p.render(id, frame))?;
VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
Ok(())
} else {
Err(ViewProcessOffline)
}
}
pub fn render_update(&self, frame: FrameUpdateRequest) -> Result<()> {
let _s = tracing::debug_span!("ViewRenderer.render_update").entered();
if let Some(w) = self.0.upgrade() {
w.call(|id, p| p.render_update(id, frame))?;
VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
Ok(())
} else {
Err(ViewProcessOffline)
}
}
pub fn render_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
if let Some(w) = self.0.upgrade() {
w.call(|id, p| p.render_extension(id, extension_id, request))
} else {
Err(ViewProcessOffline)
}
}
pub fn render_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
where
I: serde::Serialize,
O: serde::de::DeserializeOwned,
{
let r = self.render_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
Ok(r.deserialize())
}
}
#[must_use = "the image is disposed when all clones of the handle are dropped"]
#[derive(Clone)]
pub struct ViewImage(Arc<RwLock<ViewImageData>>);
impl PartialEq for ViewImage {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for ViewImage {}
impl std::hash::Hash for ViewImage {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let ptr = Arc::as_ptr(&self.0) as usize;
ptr.hash(state)
}
}
impl fmt::Debug for ViewImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ViewImage")
.field("loaded", &self.is_loaded())
.field("error", &self.error())
.field("size", &self.size())
.field("dpi", &self.ppi())
.field("is_opaque", &self.is_opaque())
.field("is_mask", &self.is_mask())
.field("generation", &self.generation())
.finish_non_exhaustive()
}
}
struct ViewImageData {
app_id: Option<AppId>,
id: Option<ImageId>,
generation: ViewProcessGen,
size: PxSize,
partial_size: PxSize,
ppi: Option<ImagePpi>,
is_opaque: bool,
partial_pixels: Option<IpcBytes>,
pixels: Option<std::result::Result<IpcBytes, Txt>>,
is_mask: bool,
done_signal: SignalOnce,
}
impl Drop for ViewImageData {
fn drop(&mut self) {
if let Some(id) = self.id {
let app_id = self.app_id.unwrap();
if let Some(app) = APP.id() {
if app_id != app {
tracing::error!("image from app `{:?}` dropped in app `{:?}`", app_id, app);
}
if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == self.generation {
let _ = VIEW_PROCESS.write().process.forget_image(id);
}
}
}
}
}
impl ViewImage {
pub fn id(&self) -> Option<ImageId> {
self.0.read().id
}
pub fn is_dummy(&self) -> bool {
self.0.read().id.is_none()
}
pub fn is_loaded(&self) -> bool {
self.0.read().pixels.as_ref().map(|r| r.is_ok()).unwrap_or(false)
}
pub fn is_partially_loaded(&self) -> bool {
self.0.read().partial_pixels.is_some()
}
pub fn is_error(&self) -> bool {
self.0.read().pixels.as_ref().map(|r| r.is_err()).unwrap_or(false)
}
pub fn error(&self) -> Option<Txt> {
self.0.read().pixels.as_ref().and_then(|s| s.as_ref().err().cloned())
}
pub fn size(&self) -> PxSize {
self.0.read().size
}
pub fn partial_size(&self) -> PxSize {
self.0.read().partial_size
}
pub fn ppi(&self) -> Option<ImagePpi> {
self.0.read().ppi
}
pub fn is_opaque(&self) -> bool {
self.0.read().is_opaque
}
pub fn is_mask(&self) -> bool {
self.0.read().is_mask
}
pub fn partial_pixels(&self) -> Option<Vec<u8>> {
self.0.read().partial_pixels.as_ref().map(|r| r[..].to_vec())
}
pub fn pixels(&self) -> Option<IpcBytes> {
self.0.read().pixels.as_ref().and_then(|r| r.as_ref().ok()).cloned()
}
pub fn app_id(&self) -> Option<AppId> {
self.0.read().app_id
}
pub fn generation(&self) -> ViewProcessGen {
self.0.read().generation
}
pub fn downgrade(&self) -> WeakViewImage {
WeakViewImage(Arc::downgrade(&self.0))
}
pub fn dummy(error: Option<Txt>) -> Self {
ViewImage(Arc::new(RwLock::new(ViewImageData {
app_id: None,
id: None,
generation: ViewProcessGen::INVALID,
size: PxSize::zero(),
partial_size: PxSize::zero(),
ppi: None,
is_opaque: true,
partial_pixels: None,
pixels: if let Some(e) = error {
Some(Err(e))
} else {
Some(Ok(IpcBytes::from_slice(&[])))
},
is_mask: false,
done_signal: SignalOnce::new_set(),
})))
}
pub fn awaiter(&self) -> SignalOnce {
self.0.read().done_signal.clone()
}
pub async fn encode(&self, format: Txt) -> std::result::Result<IpcBytes, EncodeError> {
self.awaiter().await;
if let Some(e) = self.error() {
return Err(EncodeError::Encode(e));
}
let receiver = {
let img = self.0.read();
if let Some(id) = img.id {
let mut app = VIEW_PROCESS.handle_write(img.app_id.unwrap());
app.process.encode_image(id, format.clone())?;
let (sender, receiver) = flume::bounded(1);
if let Some(entry) = app.encoding_images.iter_mut().find(|r| r.image_id == id && r.format == format) {
entry.listeners.push(sender);
} else {
app.encoding_images.push(EncodeRequest {
image_id: id,
format,
listeners: vec![sender],
});
}
receiver
} else {
return Err(EncodeError::Dummy);
}
};
receiver.recv_async().await?
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EncodeError {
Encode(Txt),
Dummy,
ViewProcessOffline,
}
impl From<Txt> for EncodeError {
fn from(e: Txt) -> Self {
EncodeError::Encode(e)
}
}
impl From<ViewProcessOffline> for EncodeError {
fn from(_: ViewProcessOffline) -> Self {
EncodeError::ViewProcessOffline
}
}
impl From<flume::RecvError> for EncodeError {
fn from(_: flume::RecvError) -> Self {
EncodeError::ViewProcessOffline
}
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EncodeError::Encode(e) => write!(f, "{e}"),
EncodeError::Dummy => write!(f, "cannot encode dummy image"),
EncodeError::ViewProcessOffline => write!(f, "{ViewProcessOffline}"),
}
}
}
impl std::error::Error for EncodeError {}
#[derive(Clone)]
pub struct WeakViewImage(sync::Weak<RwLock<ViewImageData>>);
impl WeakViewImage {
pub fn upgrade(&self) -> Option<ViewImage> {
self.0.upgrade().map(ViewImage)
}
}
struct EncodeRequest {
image_id: ImageId,
format: Txt,
listeners: Vec<flume::Sender<std::result::Result<IpcBytes, EncodeError>>>,
}
type ClipboardResult<T> = std::result::Result<T, ClipboardError>;
pub struct ViewClipboard {}
impl ViewClipboard {
pub fn read_text(&self) -> Result<ClipboardResult<Txt>> {
match VIEW_PROCESS.try_write()?.process.read_clipboard(ClipboardType::Text)? {
Ok(ClipboardData::Text(t)) => Ok(Ok(t)),
Err(e) => Ok(Err(e)),
_ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
}
}
pub fn write_text(&self, txt: Txt) -> Result<ClipboardResult<()>> {
VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Text(txt))
}
pub fn read_image(&self) -> Result<ClipboardResult<ViewImage>> {
let mut app = VIEW_PROCESS.try_write()?;
match app.process.read_clipboard(ClipboardType::Image)? {
Ok(ClipboardData::Image(id)) => {
if id == ImageId::INVALID {
Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned invalid image"))))
} else {
let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
id: Some(id),
app_id: APP.id(),
generation: app.process.generation(),
size: PxSize::zero(),
partial_size: PxSize::zero(),
ppi: None,
is_opaque: false,
partial_pixels: None,
pixels: None,
is_mask: false,
done_signal: SignalOnce::new(),
})));
app.loading_images.push(Arc::downgrade(&img.0));
Ok(Ok(img))
}
}
Err(e) => Ok(Err(e)),
_ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
}
}
pub fn write_image(&self, img: &ViewImage) -> Result<ClipboardResult<()>> {
if img.is_loaded() {
if let Some(id) = img.id() {
return VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Image(id));
}
}
Ok(Err(ClipboardError::Other(Txt::from_static("image not loaded"))))
}
pub fn read_file_list(&self) -> Result<ClipboardResult<Vec<PathBuf>>> {
match VIEW_PROCESS.try_write()?.process.read_clipboard(ClipboardType::FileList)? {
Ok(ClipboardData::FileList(f)) => Ok(Ok(f)),
Err(e) => Ok(Err(e)),
_ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
}
}
pub fn write_file_list(&self, list: Vec<PathBuf>) -> Result<ClipboardResult<()>> {
VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::FileList(list))
}
pub fn read_extension(&self, data_type: Txt) -> Result<ClipboardResult<IpcBytes>> {
match VIEW_PROCESS
.try_write()?
.process
.read_clipboard(ClipboardType::Extension(data_type.clone()))?
{
Ok(ClipboardData::Extension { data_type: rt, data }) if rt == data_type => Ok(Ok(data)),
Err(e) => Ok(Err(e)),
_ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
}
}
pub fn write_extension(&self, data_type: Txt, data: IpcBytes) -> Result<ClipboardResult<()>> {
VIEW_PROCESS
.try_write()?
.process
.write_clipboard(ClipboardData::Extension { data_type, data })
}
}