#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
#![doc(test(no_crate_inject))]
#![warn(missing_docs)]
#![warn(unused_extern_crates)]
use std::{
fmt, mem, thread,
time::{Duration, Instant},
};
use extensions::ViewExtensions;
use gl::GlContextManager;
use image_cache::ImageCache;
use keyboard::KeyLocation;
use util::WinitToPx;
use winit::{
event::{DeviceEvent, WindowEvent},
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
keyboard::ModifiersState,
monitor::MonitorHandle,
};
#[cfg(not(target_os = "android"))]
use winit::platform::modifier_supplement::KeyEventExtModifierSupplement;
#[cfg(target_os = "android")]
use winit::platform::android::EventLoopBuilderExtAndroid;
mod config;
mod display_list;
mod gl;
mod image_cache;
mod low_memory;
mod px_wr;
mod surface;
mod util;
mod window;
use surface::*;
pub mod extensions;
pub mod platform;
#[doc(no_inline)]
pub use webrender;
#[doc(no_inline)]
pub use gleam;
use webrender::api::*;
use window::Window;
use zng_txt::Txt;
use zng_unit::{Dip, DipPoint, DipRect, DipSideOffsets, DipSize, Factor, Px, PxPoint, PxRect, PxToDip};
use zng_view_api::{
api_extension::{ApiExtensionId, ApiExtensionPayload},
dialog::{DialogId, FileDialog, MsgDialog, MsgDialogResponse},
font::{FontFaceId, FontId, FontOptions, FontVariationName},
image::{ImageId, ImageLoadedData, ImageMaskMode, ImageRequest, ImageTextureId},
ipc::{IpcBytes, IpcBytesReceiver},
keyboard::{Key, KeyCode, KeyState},
mouse::ButtonId,
touch::{TouchId, TouchUpdate},
window::{
CursorIcon, CursorImage, EventCause, EventFrameRendered, FocusIndicator, FrameRequest, FrameUpdateRequest, FrameWaitId,
HeadlessOpenData, HeadlessRequest, MonitorId, MonitorInfo, VideoMode, WindowChanged, WindowId, WindowOpenData, WindowRequest,
WindowState, WindowStateAll,
},
Inited, *,
};
use rustc_hash::FxHashMap;
#[cfg(ipc)]
zng_env::on_process_start!(|_| {
if std::env::var("ZNG_VIEW_NO_INIT_START").is_err() {
view_process_main();
}
});
#[cfg(ipc)]
pub fn view_process_main() {
let config = match ViewConfig::from_env() {
Some(c) => c,
None => return,
};
std::panic::set_hook(Box::new(init_abort));
config.assert_version(false);
let c = ipc::connect_view_process(config.server_name).expect("failed to connect to app-process");
let mut ext = ViewExtensions::new();
for e in extensions::VIEW_EXTENSIONS {
e(&mut ext);
}
if config.headless {
App::run_headless(c, ext);
} else {
App::run_headed(c, ext);
}
zng_env::exit(0)
}
#[cfg(ipc)]
#[doc(hidden)]
#[no_mangle]
pub extern "C" fn extern_view_process_main() {
std::panic::set_hook(Box::new(ffi_abort));
view_process_main()
}
pub fn run_same_process(run_app: impl FnOnce() + Send + 'static) {
run_same_process_extended(run_app, ViewExtensions::new)
}
pub fn run_same_process_extended(run_app: impl FnOnce() + Send + 'static, ext: fn() -> ViewExtensions) {
let app_thread = thread::Builder::new()
.name("app".to_owned())
.spawn(move || {
if let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(run_app)) {
thread::spawn(|| {
thread::sleep(std::time::Duration::from_secs(5));
eprintln!("run_same_process did not exit after 5s of a fatal panic, exiting now");
zng_env::exit(101);
});
std::panic::resume_unwind(e);
}
})
.unwrap();
let config = ViewConfig::wait_same_process();
config.assert_version(true);
let c = ipc::connect_view_process(config.server_name).expect("failed to connect to app in same process");
let mut ext = ext();
for e in extensions::VIEW_EXTENSIONS {
e(&mut ext);
}
if config.headless {
App::run_headless(c, ext);
} else {
App::run_headed(c, ext);
}
if let Err(p) = app_thread.join() {
std::panic::resume_unwind(p);
}
}
#[cfg(ipc)]
#[doc(hidden)]
#[no_mangle]
pub extern "C" fn extern_run_same_process(patch: &StaticPatch, run_app: extern "C" fn()) {
std::panic::set_hook(Box::new(ffi_abort));
unsafe {
patch.install();
}
#[expect(clippy::redundant_closure)] run_same_process(move || run_app())
}
#[cfg(ipc)]
fn init_abort(info: &std::panic::PanicHookInfo) {
panic_hook(info, "note: aborting to respawn");
}
#[cfg(ipc)]
fn ffi_abort(info: &std::panic::PanicHookInfo) {
panic_hook(info, "note: aborting to avoid unwind across FFI");
}
#[cfg(ipc)]
fn panic_hook(info: &std::panic::PanicHookInfo, details: &str) {
let panic = util::SuppressedPanic::from_hook(info, std::backtrace::Backtrace::force_capture());
if crate::util::suppress_panic() {
crate::util::set_suppressed_panic(panic);
} else {
eprintln!("{panic}\n{details}");
zng_env::exit(101) }
}
pub(crate) struct App {
headless: bool,
exts: ViewExtensions,
gl_manager: GlContextManager,
winit_loop: util::WinitEventLoop,
idle: IdleTrace,
app_sender: AppEventSender,
request_recv: flume::Receiver<RequestEvent>,
response_sender: ipc::ResponseSender,
event_sender: ipc::EventSender,
image_cache: ImageCache,
generation: ViewProcessGen,
device_events: bool,
windows: Vec<Window>,
surfaces: Vec<Surface>,
monitor_id_gen: MonitorId,
pub monitors: Vec<(MonitorId, MonitorHandle)>,
device_id_gen: DeviceId,
devices: Vec<(DeviceId, winit::event::DeviceId)>,
dialog_id_gen: DialogId,
resize_frame_wait_id_gen: FrameWaitId,
coalescing_event: Option<(Event, Instant)>,
cursor_entered_expect_move: Vec<WindowId>,
#[cfg(windows)]
skip_ralt: bool,
pressed_modifiers: FxHashMap<(Key, KeyLocation), (DeviceId, KeyCode)>,
pending_modifiers_update: Option<ModifiersState>,
pending_modifiers_focus_clear: bool,
#[cfg(not(any(windows, target_os = "android")))]
arboard: Option<arboard::Clipboard>,
#[cfg(windows)]
low_memory_monitor: Option<low_memory::LowMemoryMonitor>,
config_listener_exit: Option<Box<dyn FnOnce()>>,
app_state: AppState,
exited: bool,
}
impl fmt::Debug for App {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HeadlessBackend")
.field("app_state", &self.app_state)
.field("generation", &self.generation)
.field("device_events", &self.device_events)
.field("windows", &self.windows)
.field("surfaces", &self.surfaces)
.finish_non_exhaustive()
}
}
impl winit::application::ApplicationHandler<AppEvent> for App {
fn resumed(&mut self, winit_loop: &ActiveEventLoop) {
if let AppState::Suspended = self.app_state {
let mut winit_loop_guard = self.winit_loop.set(winit_loop);
self.exts.resumed();
self.generation = self.generation.next();
let available_monitors = self.available_monitors();
self.notify(Event::Inited(Inited {
generation: self.generation,
is_respawn: true,
available_monitors,
multi_click_config: config::multi_click_config(),
key_repeat_config: config::key_repeat_config(),
touch_config: config::touch_config(),
font_aa: config::font_aa(),
animations_config: config::animations_config(),
locale_config: config::locale_config(),
colors_config: config::colors_config(),
chrome_config: config::chrome_config(),
extensions: self.exts.api_extensions(),
}));
winit_loop_guard.unset(&mut self.winit_loop);
} else {
self.exts.init(&self.app_sender);
}
self.app_state = AppState::Resumed;
self.update_memory_monitor(winit_loop);
}
fn window_event(&mut self, winit_loop: &ActiveEventLoop, window_id: winit::window::WindowId, event: WindowEvent) {
let i = if let Some((i, _)) = self.windows.iter_mut().enumerate().find(|(_, w)| w.window_id() == window_id) {
i
} else {
return;
};
let _s = tracing::trace_span!("on_window_event", ?event).entered();
let mut winit_loop_guard = self.winit_loop.set(winit_loop);
self.windows[i].on_window_event(&event);
let id = self.windows[i].id();
let scale_factor = self.windows[i].scale_factor();
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
let modal_dialog_active = self.windows[i].modal_dialog_active();
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
macro_rules! linux_modal_dialog_bail {
() => {
if modal_dialog_active {
winit_loop_guard.unset(&mut self.winit_loop);
return;
}
};
}
#[cfg(not(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)))]
macro_rules! linux_modal_dialog_bail {
() => {};
}
match event {
WindowEvent::RedrawRequested => self.windows[i].redraw(),
WindowEvent::Resized(_) => {
let size = if let Some(size) = self.windows[i].resized() {
size
} else {
winit_loop_guard.unset(&mut self.winit_loop);
return;
};
let deadline = Instant::now() + Duration::from_millis(300);
if self.windows[i].is_rendering_frame() {
tracing::debug!("resize requested while still rendering");
while let Ok(req) = self.request_recv.recv_deadline(deadline) {
match req {
RequestEvent::Request(req) => {
let rsp = self.respond(req);
if rsp.must_be_send() {
let _ = self.response_sender.send(rsp);
}
}
RequestEvent::FrameReady(id, msg) => {
self.on_frame_ready(id, msg);
if id == self.windows[i].id() {
break;
}
}
}
}
}
if let Some(state) = self.windows[i].state_change() {
self.notify(Event::WindowChanged(WindowChanged::state_changed(id, state, EventCause::System)));
}
if let Some(handle) = self.windows[i].monitor_change() {
let m_id = self.monitor_handle_to_id(&handle);
self.notify(Event::WindowChanged(WindowChanged::monitor_changed(id, m_id, EventCause::System)));
}
let wait_id = Some(self.resize_frame_wait_id_gen.incr());
self.notify(Event::WindowChanged(WindowChanged::resized(id, size, EventCause::System, wait_id)));
self.flush_coalesced();
let mut received_frame = false;
loop {
match self.request_recv.recv_deadline(deadline) {
Ok(req) => {
match req {
RequestEvent::Request(req) => {
received_frame = req.is_frame(id, wait_id);
if received_frame || req.affects_window_rect(id) {
let rsp = self.respond(req);
if rsp.must_be_send() {
let _ = self.response_sender.send(rsp);
}
break;
} else {
let rsp = self.respond(req);
if rsp.must_be_send() {
let _ = self.response_sender.send(rsp);
}
}
}
RequestEvent::FrameReady(id, msg) => self.on_frame_ready(id, msg),
}
}
Err(flume::RecvTimeoutError::Timeout) => {
break;
}
Err(flume::RecvTimeoutError::Disconnected) => {
winit_loop_guard.unset(&mut self.winit_loop);
unreachable!()
}
}
}
if received_frame && deadline > Instant::now() {
while let Ok(req) = self.request_recv.recv_deadline(deadline) {
match req {
RequestEvent::Request(req) => {
let rsp = self.respond(req);
if rsp.must_be_send() {
let _ = self.response_sender.send(rsp);
}
}
RequestEvent::FrameReady(id, msg) => {
self.on_frame_ready(id, msg);
if id == self.windows[i].id() {
break;
}
}
}
}
}
}
WindowEvent::Moved(_) => {
let (global_position, position) = if let Some(p) = self.windows[i].moved() {
p
} else {
winit_loop_guard.unset(&mut self.winit_loop);
return;
};
if let Some(state) = self.windows[i].state_change() {
self.notify(Event::WindowChanged(WindowChanged::state_changed(id, state, EventCause::System)));
}
self.notify(Event::WindowChanged(WindowChanged::moved(
id,
global_position,
position,
EventCause::System,
)));
if let Some(handle) = self.windows[i].monitor_change() {
let m_id = self.monitor_handle_to_id(&handle);
self.notify(Event::WindowChanged(WindowChanged::monitor_changed(id, m_id, EventCause::System)));
}
}
WindowEvent::CloseRequested => {
linux_modal_dialog_bail!();
self.notify(Event::WindowCloseRequested(id))
}
WindowEvent::Destroyed => {
self.windows.remove(i);
self.notify(Event::WindowClosed(id));
}
WindowEvent::DroppedFile(file) => {
linux_modal_dialog_bail!();
self.notify(Event::DroppedFile { window: id, file })
}
WindowEvent::HoveredFile(file) => {
linux_modal_dialog_bail!();
self.notify(Event::HoveredFile { window: id, file })
}
WindowEvent::HoveredFileCancelled => {
linux_modal_dialog_bail!();
self.notify(Event::HoveredFileCancelled(id))
}
WindowEvent::Focused(mut focused) => {
if self.windows[i].focused_changed(&mut focused) {
if focused {
self.notify(Event::FocusChanged { prev: None, new: Some(id) });
if let Some(state) = self.windows[i].state_change() {
self.notify(Event::WindowChanged(WindowChanged::state_changed(id, state, EventCause::System)));
}
} else {
self.pending_modifiers_focus_clear = true;
self.notify(Event::FocusChanged { prev: Some(id), new: None });
}
}
}
WindowEvent::KeyboardInput {
device_id,
event,
is_synthetic,
} => {
linux_modal_dialog_bail!();
if !is_synthetic && self.windows[i].is_focused() {
#[cfg(windows)]
if self.skip_ralt {
if let winit::keyboard::PhysicalKey::Code(winit::keyboard::KeyCode::AltRight) = event.physical_key {
winit_loop_guard.unset(&mut self.winit_loop);
return;
}
}
let state = util::element_state_to_key_state(event.state);
#[cfg(not(target_os = "android"))]
let key = util::winit_key_to_key(event.key_without_modifiers());
let key_modified = util::winit_key_to_key(event.logical_key);
#[cfg(target_os = "android")]
let key = key_modified.clone();
let key_code = util::winit_physical_key_to_key_code(event.physical_key);
let key_location = util::winit_key_location_to_zng(event.location);
let d_id = self.device_id(device_id);
let mut send_event = true;
if key.is_modifier() {
match state {
KeyState::Pressed => {
send_event = self
.pressed_modifiers
.insert((key.clone(), key_location), (d_id, key_code))
.is_none();
}
KeyState::Released => send_event = self.pressed_modifiers.remove(&(key.clone(), key_location)).is_some(),
}
}
if send_event {
self.notify(Event::KeyboardInput {
window: id,
device: d_id,
key_code,
key_location,
state,
text: match event.text {
Some(s) => Txt::from_str(s.as_str()),
#[cfg(target_os = "android")]
None => match (state, &key) {
(KeyState::Pressed, Key::Char(c)) => Txt::from(*c),
(KeyState::Pressed, Key::Str(s)) => s.clone(),
_ => Txt::default(),
},
#[cfg(not(target_os = "android"))]
None => Txt::default(),
},
key,
key_modified,
});
}
}
}
WindowEvent::ModifiersChanged(m) => {
linux_modal_dialog_bail!();
if self.windows[i].is_focused() {
self.pending_modifiers_update = Some(m.state());
}
}
WindowEvent::CursorMoved { device_id, position, .. } => {
linux_modal_dialog_bail!();
let px_p = position.to_px();
let p = px_p.to_dip(scale_factor);
let d_id = self.device_id(device_id);
let mut is_after_cursor_enter = false;
if let Some(i) = self.cursor_entered_expect_move.iter().position(|&w| w == id) {
self.cursor_entered_expect_move.remove(i);
is_after_cursor_enter = true;
}
if self.windows[i].cursor_moved(p, d_id) || is_after_cursor_enter {
self.notify(Event::MouseMoved {
window: id,
device: d_id,
coalesced_pos: vec![],
position: p,
});
}
}
WindowEvent::CursorEntered { device_id } => {
linux_modal_dialog_bail!();
if self.windows[i].cursor_entered() {
let d_id = self.device_id(device_id);
self.notify(Event::MouseEntered { window: id, device: d_id });
self.cursor_entered_expect_move.push(id);
}
}
WindowEvent::CursorLeft { device_id } => {
linux_modal_dialog_bail!();
if self.windows[i].cursor_left() {
let d_id = self.device_id(device_id);
self.notify(Event::MouseLeft { window: id, device: d_id });
if let Some(i) = self.cursor_entered_expect_move.iter().position(|&w| w == id) {
self.cursor_entered_expect_move.remove(i);
}
}
}
WindowEvent::MouseWheel {
device_id, delta, phase, ..
} => {
linux_modal_dialog_bail!();
let d_id = self.device_id(device_id);
self.notify(Event::MouseWheel {
window: id,
device: d_id,
delta: util::winit_mouse_wheel_delta_to_zng(delta),
phase: util::winit_touch_phase_to_zng(phase),
});
}
WindowEvent::MouseInput {
device_id, state, button, ..
} => {
linux_modal_dialog_bail!();
let d_id = self.device_id(device_id);
self.notify(Event::MouseInput {
window: id,
device: d_id,
state: util::element_state_to_button_state(state),
button: util::winit_mouse_button_to_zng(button),
});
}
WindowEvent::TouchpadPressure {
device_id,
pressure,
stage,
} => {
linux_modal_dialog_bail!();
let d_id = self.device_id(device_id);
self.notify(Event::TouchpadPressure {
window: id,
device: d_id,
pressure,
stage,
});
}
WindowEvent::AxisMotion { device_id, axis, value } => {
linux_modal_dialog_bail!();
let d_id = self.device_id(device_id);
self.notify(Event::AxisMotion {
window: id,
device: d_id,
axis: AxisId(axis),
value,
});
}
WindowEvent::Touch(t) => {
let d_id = self.device_id(t.device_id);
let position = t.location.to_px().to_dip(scale_factor);
let notify = match t.phase {
winit::event::TouchPhase::Moved => self.windows[i].touch_moved(position, d_id, t.id),
winit::event::TouchPhase::Started => true,
winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
self.windows[i].touch_end(d_id, t.id);
true
}
};
if notify {
self.notify(Event::Touch {
window: id,
device: d_id,
touches: vec![TouchUpdate {
phase: util::winit_touch_phase_to_zng(t.phase),
position,
force: t.force.map(util::winit_force_to_zng),
touch: TouchId(t.id),
}],
});
}
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
let monitor;
let mut is_monitor_change = false;
if let Some(new_monitor) = self.windows[i].monitor_change() {
monitor = Some(new_monitor);
is_monitor_change = true;
} else {
monitor = self.windows[i].monitor();
}
let monitor = if let Some(handle) = monitor {
self.monitor_handle_to_id(&handle)
} else {
MonitorId::INVALID
};
if is_monitor_change {
self.notify(Event::WindowChanged(WindowChanged::monitor_changed(
id,
monitor,
EventCause::System,
)));
}
self.notify(Event::ScaleFactorChanged {
monitor,
windows: vec![id],
scale_factor: scale_factor as f32,
});
if let Some(size) = self.windows[i].resized() {
self.notify(Event::WindowChanged(WindowChanged::resized(id, size, EventCause::System, None)));
}
}
WindowEvent::Ime(ime) => {
linux_modal_dialog_bail!();
match ime {
winit::event::Ime::Preedit(s, c) => {
let caret = c.unwrap_or((s.len(), s.len()));
let ime = Ime::Preview(s.into(), caret);
self.notify(Event::Ime { window: id, ime });
}
winit::event::Ime::Commit(s) => {
let ime = Ime::Commit(s.into());
self.notify(Event::Ime { window: id, ime });
}
winit::event::Ime::Enabled => {}
winit::event::Ime::Disabled => {}
}
}
WindowEvent::ThemeChanged(_) => {}
WindowEvent::Occluded(_) => {}
WindowEvent::ActivationTokenDone { .. } => {}
WindowEvent::PinchGesture { .. } => {}
WindowEvent::RotationGesture { .. } => {}
WindowEvent::DoubleTapGesture { .. } => {}
WindowEvent::PanGesture { .. } => {}
}
winit_loop_guard.unset(&mut self.winit_loop);
}
fn new_events(&mut self, _winit_loop: &ActiveEventLoop, _cause: winit::event::StartCause) {
self.idle.exit();
#[cfg(windows)]
if let winit::event::StartCause::ResumeTimeReached { .. } = _cause {
self.update_memory_monitor(_winit_loop);
}
}
fn user_event(&mut self, winit_loop: &ActiveEventLoop, ev: AppEvent) {
let mut winit_loop_guard = self.winit_loop.set(winit_loop);
match ev {
AppEvent::Request => {
while let Ok(req) = self.request_recv.try_recv() {
match req {
RequestEvent::Request(req) => {
let rsp = self.respond(req);
if rsp.must_be_send() && self.response_sender.send(rsp).is_err() {
self.exited = true;
self.winit_loop.exit();
}
}
RequestEvent::FrameReady(wid, msg) => self.on_frame_ready(wid, msg),
}
}
}
AppEvent::Notify(ev) => self.notify(ev),
AppEvent::WinitFocused(window_id, focused) => self.window_event(winit_loop, window_id, WindowEvent::Focused(focused)),
AppEvent::RefreshMonitors => self.refresh_monitors(),
AppEvent::ParentProcessExited => {
self.exited = true;
self.winit_loop.exit();
}
AppEvent::ImageLoaded(data) => {
self.image_cache.loaded(data);
}
AppEvent::MonitorPowerChanged => {
for w in &mut self.windows {
w.redraw();
}
}
AppEvent::InitDeviceEvents(enabled) => {
self.init_device_events(enabled, Some(winit_loop));
}
}
winit_loop_guard.unset(&mut self.winit_loop);
}
fn device_event(&mut self, winit_loop: &ActiveEventLoop, device_id: winit::event::DeviceId, event: DeviceEvent) {
if self.device_events {
let _s = tracing::trace_span!("on_device_event", ?event);
let mut winit_loop_guard = self.winit_loop.set(winit_loop);
let d_id = self.device_id(device_id);
match event {
DeviceEvent::Added => self.notify(Event::DeviceAdded(d_id)),
DeviceEvent::Removed => self.notify(Event::DeviceRemoved(d_id)),
DeviceEvent::MouseMotion { delta } => self.notify(Event::DeviceMouseMotion {
device: d_id,
delta: euclid::vec2(delta.0, delta.1),
}),
DeviceEvent::MouseWheel { delta } => self.notify(Event::DeviceMouseWheel {
device: d_id,
delta: util::winit_mouse_wheel_delta_to_zng(delta),
}),
DeviceEvent::Motion { axis, value } => self.notify(Event::DeviceMotion {
device: d_id,
axis: AxisId(axis),
value,
}),
DeviceEvent::Button { button, state } => self.notify(Event::DeviceButton {
device: d_id,
button: ButtonId(button),
state: util::element_state_to_button_state(state),
}),
DeviceEvent::Key(k) => self.notify(Event::DeviceKey {
device: d_id,
key_code: util::winit_physical_key_to_key_code(k.physical_key),
state: util::element_state_to_key_state(k.state),
}),
}
winit_loop_guard.unset(&mut self.winit_loop);
}
}
fn about_to_wait(&mut self, winit_loop: &ActiveEventLoop) {
let mut winit_loop_guard = self.winit_loop.set(winit_loop);
self.finish_cursor_entered_move();
self.update_modifiers();
self.flush_coalesced();
#[cfg(windows)]
{
self.skip_ralt = false;
}
self.idle.enter();
winit_loop_guard.unset(&mut self.winit_loop);
}
fn suspended(&mut self, _: &ActiveEventLoop) {
#[cfg(target_os = "android")]
if let Some(w) = &self.windows.first() {
self.notify(Event::FocusChanged {
prev: Some(w.id()),
new: None,
});
}
self.app_state = AppState::Suspended;
self.windows.clear();
self.surfaces.clear();
self.image_cache.clear();
self.exts.suspended();
self.notify(Event::Suspended);
}
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
let _ = event_loop;
if let Some(t) = self.config_listener_exit.take() {
t();
}
}
fn memory_warning(&mut self, winit_loop: &ActiveEventLoop) {
let mut winit_loop_guard = self.winit_loop.set(winit_loop);
self.image_cache.on_low_memory();
for w in &mut self.windows {
w.on_low_memory();
}
for s in &mut self.surfaces {
s.on_low_memory();
}
self.exts.on_low_memory();
self.notify(Event::LowMemory);
winit_loop_guard.unset(&mut self.winit_loop);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AppState {
PreInitSuspended,
Resumed,
Suspended,
}
struct IdleTrace(Option<tracing::span::EnteredSpan>);
impl IdleTrace {
pub fn enter(&mut self) {
self.0 = Some(tracing::trace_span!("<winit-idle>").entered());
}
pub fn exit(&mut self) {
self.0 = None;
}
}
impl App {
fn init_device_events(&mut self, enabled: bool, t: Option<&ActiveEventLoop>) {
self.device_events = enabled;
if let Some(t) = t {
if enabled {
t.listen_device_events(winit::event_loop::DeviceEvents::Always);
} else {
t.listen_device_events(winit::event_loop::DeviceEvents::Never);
}
} else {
self.device_events = false;
}
}
pub fn run_headless(ipc: ipc::ViewChannels, ext: ViewExtensions) {
tracing::info!("running headless view-process");
gl::warmup();
let (app_sender, app_receiver) = flume::unbounded();
let (request_sender, request_receiver) = flume::unbounded();
let mut app = App::new(
AppEventSender::Headless(app_sender, request_sender),
ipc.response_sender,
ipc.event_sender,
request_receiver,
ext,
);
app.headless = true;
let winit_span = tracing::trace_span!("winit::EventLoop::new").entered();
#[cfg(not(target_os = "android"))]
let event_loop = EventLoop::builder().build().unwrap();
#[cfg(target_os = "android")]
let event_loop = EventLoop::builder()
.with_android_app(platform::android::android_app())
.build()
.unwrap();
drop(winit_span);
let mut app = HeadlessApp {
app,
request_receiver: Some(ipc.request_receiver),
app_receiver,
};
if let Err(e) = event_loop.run_app(&mut app) {
if app.app.exited {
tracing::error!("winit event loop error after app exit, {e}");
} else {
panic!("winit event loop error, {e}");
}
}
struct HeadlessApp {
app: App,
request_receiver: Option<ipc::RequestReceiver>,
app_receiver: flume::Receiver<AppEvent>,
}
impl winit::application::ApplicationHandler<()> for HeadlessApp {
fn resumed(&mut self, winit_loop: &ActiveEventLoop) {
let mut winit_loop_guard = self.app.winit_loop.set(winit_loop);
self.app.resumed(winit_loop);
self.app.start_receiving(self.request_receiver.take().unwrap());
'app_loop: while !self.app.exited {
match self.app_receiver.recv() {
Ok(app_ev) => match app_ev {
AppEvent::Request => {
while let Ok(request) = self.app.request_recv.try_recv() {
match request {
RequestEvent::Request(request) => {
let response = self.app.respond(request);
if response.must_be_send() && self.app.response_sender.send(response).is_err() {
self.app.exited = true;
break 'app_loop;
}
}
RequestEvent::FrameReady(id, msg) => {
let r = if let Some(s) = self.app.surfaces.iter_mut().find(|s| s.id() == id) {
Some(s.on_frame_ready(msg, &mut self.app.image_cache))
} else {
None
};
if let Some((frame_id, image)) = r {
self.app.notify(Event::FrameRendered(EventFrameRendered {
window: id,
frame: frame_id,
frame_image: image,
}));
}
}
}
}
}
AppEvent::Notify(ev) => {
if self.app.event_sender.send(ev).is_err() {
self.app.exited = true;
break 'app_loop;
}
}
AppEvent::RefreshMonitors => {
panic!("no monitor info in headless mode")
}
AppEvent::WinitFocused(_, _) => {
panic!("no winit event loop in headless mode")
}
AppEvent::ParentProcessExited => {
self.app.exited = true;
break 'app_loop;
}
AppEvent::ImageLoaded(data) => {
self.app.image_cache.loaded(data);
}
AppEvent::MonitorPowerChanged => {} AppEvent::InitDeviceEvents(enabled) => {
self.app.init_device_events(enabled, None);
}
},
Err(_) => {
self.app.exited = true;
break 'app_loop;
}
}
}
self.app.winit_loop.exit();
winit_loop_guard.unset(&mut self.app.winit_loop);
}
fn window_event(&mut self, _: &ActiveEventLoop, _: winit::window::WindowId, _: WindowEvent) {}
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
self.app.suspended(event_loop);
}
}
}
pub fn run_headed(ipc: ipc::ViewChannels, ext: ViewExtensions) {
tracing::info!("running headed view-process");
gl::warmup();
let winit_span = tracing::trace_span!("winit::EventLoop::new").entered();
#[cfg(not(target_os = "android"))]
let event_loop = EventLoop::with_user_event().build().unwrap();
#[cfg(target_os = "android")]
let event_loop = EventLoop::with_user_event()
.with_android_app(platform::android::android_app())
.build()
.unwrap();
drop(winit_span);
let app_sender = event_loop.create_proxy();
let (request_sender, request_receiver) = flume::unbounded();
let mut app = App::new(
AppEventSender::Headed(app_sender, request_sender),
ipc.response_sender,
ipc.event_sender,
request_receiver,
ext,
);
app.start_receiving(ipc.request_receiver);
app.config_listener_exit = config::spawn_listener(app.app_sender.clone());
if let Err(e) = event_loop.run_app(&mut app) {
if app.exited {
tracing::error!("winit event loop error after app exit, {e}");
} else {
panic!("winit event loop error, {e}");
}
}
}
fn new(
app_sender: AppEventSender,
response_sender: ipc::ResponseSender,
event_sender: ipc::EventSender,
request_recv: flume::Receiver<RequestEvent>,
mut exts: ViewExtensions,
) -> Self {
exts.renderer("zng-view.webrender_debug", extensions::RendererDebugExt::new);
#[cfg(windows)]
{
exts.window("zng-view.prefer_angle", extensions::PreferAngleExt::new);
}
let mut idle = IdleTrace(None);
idle.enter();
App {
headless: false,
exts,
idle,
gl_manager: GlContextManager::default(),
image_cache: ImageCache::new(app_sender.clone()),
app_sender,
request_recv,
response_sender,
event_sender,
winit_loop: util::WinitEventLoop::default(),
generation: ViewProcessGen::INVALID,
device_events: false,
windows: vec![],
surfaces: vec![],
monitors: vec![],
monitor_id_gen: MonitorId::INVALID,
devices: vec![],
device_id_gen: DeviceId::INVALID,
dialog_id_gen: DialogId::INVALID,
resize_frame_wait_id_gen: FrameWaitId::INVALID,
coalescing_event: None,
cursor_entered_expect_move: Vec::with_capacity(1),
app_state: AppState::PreInitSuspended,
exited: false,
#[cfg(windows)]
skip_ralt: false,
pressed_modifiers: FxHashMap::default(),
pending_modifiers_update: None,
pending_modifiers_focus_clear: false,
config_listener_exit: None,
#[cfg(not(any(windows, target_os = "android")))]
arboard: None,
#[cfg(windows)]
low_memory_monitor: low_memory::LowMemoryMonitor::new(),
}
}
fn start_receiving(&mut self, mut request_recv: ipc::RequestReceiver) {
let app_sender = self.app_sender.clone();
thread::spawn(move || {
while let Ok(r) = request_recv.recv() {
if let Err(ipc::Disconnected) = app_sender.request(r) {
break;
}
}
let _ = app_sender.send(AppEvent::ParentProcessExited);
});
}
fn monitor_handle_to_id(&mut self, handle: &MonitorHandle) -> MonitorId {
if let Some((id, _)) = self.monitors.iter().find(|(_, h)| h == handle) {
*id
} else {
self.refresh_monitors();
if let Some((id, _)) = self.monitors.iter().find(|(_, h)| h == handle) {
*id
} else {
MonitorId::INVALID
}
}
}
fn update_modifiers(&mut self) {
if mem::take(&mut self.pending_modifiers_focus_clear) && self.windows.iter().all(|w| !w.is_focused()) {
self.pressed_modifiers.clear();
}
if let Some(m) = self.pending_modifiers_update.take() {
if let Some(id) = self.windows.iter().find(|w| w.is_focused()).map(|w| w.id()) {
let mut notify = vec![];
self.pressed_modifiers.retain(|(key, location), (d_id, s_code)| {
let mut retain = true;
if matches!(key, Key::Super) && !m.super_key() {
retain = false;
notify.push(Event::KeyboardInput {
window: id,
device: *d_id,
key_code: *s_code,
state: KeyState::Released,
key: key.clone(),
key_location: *location,
key_modified: key.clone(),
text: Txt::from_str(""),
});
}
if matches!(key, Key::Shift) && !m.shift_key() {
retain = false;
notify.push(Event::KeyboardInput {
window: id,
device: *d_id,
key_code: *s_code,
state: KeyState::Released,
key: key.clone(),
key_location: *location,
key_modified: key.clone(),
text: Txt::from_str(""),
});
}
if matches!(key, Key::Alt | Key::AltGraph) && !m.alt_key() {
retain = false;
notify.push(Event::KeyboardInput {
window: id,
device: *d_id,
key_code: *s_code,
state: KeyState::Released,
key: key.clone(),
key_location: *location,
key_modified: key.clone(),
text: Txt::from_str(""),
});
}
if matches!(key, Key::Ctrl) && !m.control_key() {
retain = false;
notify.push(Event::KeyboardInput {
window: id,
device: *d_id,
key_code: *s_code,
state: KeyState::Released,
key: key.clone(),
key_location: *location,
key_modified: key.clone(),
text: Txt::from_str(""),
});
}
retain
});
for ev in notify {
self.notify(ev);
}
}
}
}
fn refresh_monitors(&mut self) {
let mut monitors = Vec::with_capacity(self.monitors.len());
let mut changed = false;
for (fresh_handle, (id, handle)) in self.winit_loop.available_monitors().zip(&self.monitors) {
let id = if &fresh_handle == handle {
*id
} else {
changed = true;
self.monitor_id_gen.incr()
};
monitors.push((id, fresh_handle))
}
if changed {
self.monitors = monitors;
let monitors = self.available_monitors();
self.notify(Event::MonitorsChanged(monitors));
}
}
fn on_frame_ready(&mut self, window_id: WindowId, msg: FrameReadyMsg) {
let _s = tracing::trace_span!("on_frame_ready").entered();
if let Some(w) = self.windows.iter_mut().find(|w| w.id() == window_id) {
let r = w.on_frame_ready(msg, &mut self.image_cache);
let _ = self.event_sender.send(Event::FrameRendered(EventFrameRendered {
window: window_id,
frame: r.frame_id,
frame_image: r.image,
}));
if r.first_frame {
let size = w.size();
self.notify(Event::WindowChanged(WindowChanged::resized(window_id, size, EventCause::App, None)));
}
} else if let Some(s) = self.surfaces.iter_mut().find(|w| w.id() == window_id) {
let (frame_id, image) = s.on_frame_ready(msg, &mut self.image_cache);
self.notify(Event::FrameRendered(EventFrameRendered {
window: window_id,
frame: frame_id,
frame_image: image,
}))
}
}
pub(crate) fn notify(&mut self, event: Event) {
let now = Instant::now();
if let Some((mut coal, timestamp)) = self.coalescing_event.take() {
let r = if now.saturating_duration_since(timestamp) >= Duration::from_millis(16) {
Err(event)
} else {
coal.coalesce(event)
};
match r {
Ok(()) => self.coalescing_event = Some((coal, timestamp)),
Err(event) => match (&mut coal, event) {
(
Event::KeyboardInput {
window,
device,
state,
text,
..
},
Event::KeyboardInput {
window: n_window,
device: n_device,
text: n_text,
..
},
) if !n_text.is_empty() && *window == n_window && *device == n_device && *state == KeyState::Pressed => {
if text.is_empty() {
*text = n_text;
} else {
text.push_str(&n_text);
};
self.coalescing_event = Some((coal, now));
}
(_, event) => {
let mut error = self.event_sender.send(coal).is_err();
error |= self.event_sender.send(event).is_err();
if error {
let _ = self.app_sender.send(AppEvent::ParentProcessExited);
}
}
},
}
} else {
self.coalescing_event = Some((event, now));
}
if self.headless {
self.flush_coalesced();
}
}
pub(crate) fn finish_cursor_entered_move(&mut self) {
let mut moves = vec![];
for window_id in self.cursor_entered_expect_move.drain(..) {
if let Some(w) = self.windows.iter().find(|w| w.id() == window_id) {
let (position, device) = w.last_cursor_pos();
moves.push(Event::MouseMoved {
window: w.id(),
device,
coalesced_pos: vec![],
position,
});
}
}
for ev in moves {
self.notify(ev);
}
}
pub(crate) fn flush_coalesced(&mut self) {
if let Some((coal, _)) = self.coalescing_event.take() {
if self.event_sender.send(coal).is_err() {
let _ = self.app_sender.send(AppEvent::ParentProcessExited);
}
}
}
#[track_caller]
fn assert_resumed(&self) {
assert_eq!(self.app_state, AppState::Resumed);
}
fn with_window<R>(&mut self, id: WindowId, action: impl FnOnce(&mut Window) -> R, not_found: impl FnOnce() -> R) -> R {
self.assert_resumed();
self.windows.iter_mut().find(|w| w.id() == id).map(action).unwrap_or_else(|| {
tracing::error!("headed window `{id:?}` not found, will return fallback result");
not_found()
})
}
fn monitor_id(&mut self, handle: &MonitorHandle) -> MonitorId {
if let Some((id, _)) = self.monitors.iter().find(|(_, h)| h == handle) {
*id
} else {
let id = self.monitor_id_gen.incr();
self.monitors.push((id, handle.clone()));
id
}
}
fn device_id(&mut self, device_id: winit::event::DeviceId) -> DeviceId {
if let Some((id, _)) = self.devices.iter().find(|(_, id)| *id == device_id) {
*id
} else {
let id = self.device_id_gen.incr();
self.devices.push((id, device_id));
id
}
}
fn available_monitors(&mut self) -> Vec<(MonitorId, MonitorInfo)> {
let _span = tracing::trace_span!("available_monitors").entered();
let primary = self.winit_loop.primary_monitor();
self.winit_loop
.available_monitors()
.map(|m| {
let id = self.monitor_id(&m);
let is_primary = primary.as_ref().map(|h| h == &m).unwrap_or(false);
let mut info = util::monitor_handle_to_info(&m);
info.is_primary = is_primary;
(id, info)
})
.collect()
}
fn update_memory_monitor(&mut self, _winit_loop: &ActiveEventLoop) {
#[cfg(windows)]
if let Some(m) = &mut self.low_memory_monitor {
if m.notify() {
use winit::application::ApplicationHandler as _;
self.memory_warning(_winit_loop);
}
_winit_loop.set_control_flow(winit::event_loop::ControlFlow::wait_duration(Duration::from_secs(5)));
}
}
}
macro_rules! with_window_or_surface {
($self:ident, $id:ident, |$el:ident|$action:expr, ||$fallback:expr) => {
if let Some($el) = $self.windows.iter_mut().find(|w| w.id() == $id) {
$action
} else if let Some($el) = $self.surfaces.iter_mut().find(|w| w.id() == $id) {
$action
} else {
tracing::error!("window `{:?}` not found, will return fallback result", $id);
$fallback
}
};
}
impl Drop for App {
fn drop(&mut self) {
if let Some(f) = self.config_listener_exit.take() {
f();
}
}
}
impl App {
fn open_headless_impl(&mut self, config: HeadlessRequest) -> HeadlessOpenData {
self.assert_resumed();
let surf = Surface::open(
self.generation,
config,
&self.winit_loop,
&mut self.gl_manager,
self.exts.new_window(),
self.exts.new_renderer(),
self.app_sender.clone(),
);
let render_mode = surf.render_mode();
self.surfaces.push(surf);
HeadlessOpenData { render_mode }
}
#[cfg(not(any(windows, target_os = "android")))]
fn arboard(&mut self) -> Result<&mut arboard::Clipboard, clipboard::ClipboardError> {
if self.arboard.is_none() {
match arboard::Clipboard::new() {
Ok(c) => self.arboard = Some(c),
Err(e) => return Err(util::arboard_to_clip(e)),
}
}
Ok(self.arboard.as_mut().unwrap())
}
}
impl Api for App {
fn init(&mut self, gen: ViewProcessGen, is_respawn: bool, device_events: bool, headless: bool) {
if self.exited {
panic!("cannot restart exited");
}
self.generation = gen;
self.device_events = device_events;
self.headless = headless;
self.app_sender.send(AppEvent::InitDeviceEvents(device_events)).unwrap();
let available_monitors = self.available_monitors();
self.notify(Event::Inited(Inited {
generation: gen,
is_respawn,
available_monitors,
multi_click_config: config::multi_click_config(),
key_repeat_config: config::key_repeat_config(),
touch_config: config::touch_config(),
font_aa: config::font_aa(),
animations_config: config::animations_config(),
locale_config: config::locale_config(),
colors_config: config::colors_config(),
chrome_config: config::chrome_config(),
extensions: self.exts.api_extensions(),
}));
}
fn exit(&mut self) {
self.assert_resumed();
self.exited = true;
if let Some(t) = self.config_listener_exit.take() {
t();
}
let _ = self.app_sender.send(AppEvent::ParentProcessExited);
}
fn open_window(&mut self, mut config: WindowRequest) {
let _s = tracing::debug_span!("open", ?config).entered();
config.state.clamp_size();
config.enforce_kiosk();
if self.headless {
let id = config.id;
let data = self.open_headless_impl(HeadlessRequest {
id: config.id,
scale_factor: Factor(1.0),
size: config.state.restore_rect.size,
render_mode: config.render_mode,
extensions: config.extensions,
});
let msg = WindowOpenData {
render_mode: data.render_mode,
monitor: None,
position: (PxPoint::zero(), DipPoint::zero()),
size: config.state.restore_rect.size,
scale_factor: Factor(1.0),
safe_padding: DipSideOffsets::zero(),
state: WindowStateAll {
state: WindowState::Fullscreen,
global_position: PxPoint::zero(),
restore_rect: DipRect::from_size(config.state.restore_rect.size),
restore_state: WindowState::Fullscreen,
min_size: DipSize::zero(),
max_size: DipSize::new(Dip::MAX, Dip::MAX),
chrome_visible: false,
},
};
self.notify(Event::WindowOpened(id, msg));
} else {
self.assert_resumed();
#[cfg(target_os = "android")]
if !self.windows.is_empty() {
tracing::error!("android can only have one window");
return;
}
let id = config.id;
let win = Window::open(
self.generation,
config.icon.and_then(|i| self.image_cache.get(i)).and_then(|i| i.icon()),
config
.cursor_image
.and_then(|(i, h)| self.image_cache.get(i).and_then(|i| i.cursor(h, &self.winit_loop))),
config,
&self.winit_loop,
&mut self.gl_manager,
self.exts.new_window(),
self.exts.new_renderer(),
self.app_sender.clone(),
);
let msg = WindowOpenData {
monitor: win.monitor().map(|h| self.monitor_id(&h)),
position: win.inner_position(),
size: win.size(),
scale_factor: win.scale_factor(),
render_mode: win.render_mode(),
state: win.state(),
safe_padding: win.safe_padding(),
};
self.windows.push(win);
self.notify(Event::WindowOpened(id, msg));
#[cfg(target_os = "android")]
{
self.windows.last_mut().unwrap().focused_changed(&mut true);
self.notify(Event::FocusChanged { prev: None, new: Some(id) });
}
}
}
fn open_headless(&mut self, config: HeadlessRequest) {
let _s = tracing::debug_span!("open_headless", ?config).entered();
let id = config.id;
let msg = self.open_headless_impl(config);
self.notify(Event::HeadlessOpened(id, msg));
}
fn close(&mut self, id: WindowId) {
let _s = tracing::debug_span!("close_window", ?id);
self.assert_resumed();
if let Some(i) = self.windows.iter().position(|w| w.id() == id) {
let _ = self.windows.swap_remove(i);
}
if let Some(i) = self.surfaces.iter().position(|w| w.id() == id) {
let _ = self.surfaces.swap_remove(i);
}
}
fn set_title(&mut self, id: WindowId, title: Txt) {
self.with_window(id, |w| w.set_title(title), || ())
}
fn set_visible(&mut self, id: WindowId, visible: bool) {
self.with_window(id, |w| w.set_visible(visible), || ())
}
fn set_always_on_top(&mut self, id: WindowId, always_on_top: bool) {
self.with_window(id, |w| w.set_always_on_top(always_on_top), || ())
}
fn set_movable(&mut self, id: WindowId, movable: bool) {
self.with_window(id, |w| w.set_movable(movable), || ())
}
fn set_resizable(&mut self, id: WindowId, resizable: bool) {
self.with_window(id, |w| w.set_resizable(resizable), || ())
}
fn set_taskbar_visible(&mut self, id: WindowId, visible: bool) {
self.with_window(id, |w| w.set_taskbar_visible(visible), || ())
}
fn bring_to_top(&mut self, id: WindowId) {
self.with_window(id, |w| w.bring_to_top(), || ())
}
fn set_state(&mut self, id: WindowId, state: WindowStateAll) {
if let Some(w) = self.windows.iter_mut().find(|w| w.id() == id) {
if w.set_state(state.clone()) {
let mut change = WindowChanged::state_changed(id, state, EventCause::App);
change.size = w.resized();
change.position = w.moved();
if let Some(handle) = w.monitor_change() {
let monitor = self.monitor_handle_to_id(&handle);
change.monitor = Some(monitor);
}
let _ = self.app_sender.send(AppEvent::Notify(Event::WindowChanged(change)));
}
}
}
fn set_headless_size(&mut self, renderer: WindowId, size: DipSize, scale_factor: Factor) {
self.assert_resumed();
if let Some(surf) = self.surfaces.iter_mut().find(|s| s.id() == renderer) {
surf.set_size(size, scale_factor)
}
}
fn set_video_mode(&mut self, id: WindowId, mode: VideoMode) {
self.with_window(id, |w| w.set_video_mode(mode), || ())
}
fn set_icon(&mut self, id: WindowId, icon: Option<ImageId>) {
let icon = icon.and_then(|i| self.image_cache.get(i)).and_then(|i| i.icon());
self.with_window(id, |w| w.set_icon(icon), || ())
}
fn set_focus_indicator(&mut self, id: WindowId, request: Option<FocusIndicator>) {
self.with_window(id, |w| w.set_focus_request(request), || ())
}
fn focus(&mut self, id: WindowId) -> FocusResult {
#[cfg(windows)]
{
let (r, s) = self.with_window(id, |w| w.focus(), || (FocusResult::Requested, false));
self.skip_ralt = s;
r
}
#[cfg(not(windows))]
{
self.with_window(id, |w| w.focus(), || FocusResult::Requested)
}
}
fn drag_move(&mut self, id: WindowId) {
self.with_window(id, |w| w.drag_move(), || ())
}
fn drag_resize(&mut self, id: WindowId, direction: zng_view_api::window::ResizeDirection) {
self.with_window(id, |w| w.drag_resize(direction), || ())
}
fn set_enabled_buttons(&mut self, id: WindowId, buttons: zng_view_api::window::WindowButton) {
self.with_window(id, |w| w.set_enabled_buttons(buttons), || ())
}
fn open_title_bar_context_menu(&mut self, id: WindowId, position: DipPoint) {
self.with_window(id, |w| w.open_title_bar_context_menu(position), || ())
}
fn set_cursor(&mut self, id: WindowId, icon: Option<CursorIcon>) {
self.with_window(id, |w| w.set_cursor(icon), || ())
}
fn set_cursor_image(&mut self, id: WindowId, icon: Option<CursorImage>) {
let icon = icon.and_then(|img| self.image_cache.get(img.img).and_then(|i| i.cursor(img.hotspot, &self.winit_loop)));
self.with_window(id, |w| w.set_cursor_image(icon), || ());
}
fn set_ime_area(&mut self, id: WindowId, area: Option<DipRect>) {
self.with_window(id, |w| w.set_ime_area(area), || ())
}
fn image_decoders(&mut self) -> Vec<Txt> {
image_cache::DECODERS.iter().map(|&s| Txt::from_static(s)).collect()
}
fn image_encoders(&mut self) -> Vec<Txt> {
image_cache::ENCODERS.iter().map(|&s| Txt::from_static(s)).collect()
}
fn add_image(&mut self, request: ImageRequest<IpcBytes>) -> ImageId {
self.image_cache.add(request)
}
fn add_image_pro(&mut self, request: ImageRequest<IpcBytesReceiver>) -> ImageId {
self.image_cache.add_pro(request)
}
fn forget_image(&mut self, id: ImageId) {
self.image_cache.forget(id)
}
fn encode_image(&mut self, id: ImageId, format: Txt) {
self.image_cache.encode(id, format)
}
fn use_image(&mut self, id: WindowId, image_id: ImageId) -> ImageTextureId {
if let Some(img) = self.image_cache.get(image_id) {
with_window_or_surface!(self, id, |w| w.use_image(img), || ImageTextureId::INVALID)
} else {
ImageTextureId::INVALID
}
}
fn update_image_use(&mut self, id: WindowId, texture_id: ImageTextureId, image_id: ImageId) {
if let Some(img) = self.image_cache.get(image_id) {
with_window_or_surface!(self, id, |w| w.update_image(texture_id, img), || ())
}
}
fn delete_image_use(&mut self, id: WindowId, texture_id: ImageTextureId) {
with_window_or_surface!(self, id, |w| w.delete_image(texture_id), || ())
}
fn add_font_face(&mut self, id: WindowId, bytes: IpcBytes, index: u32) -> FontFaceId {
with_window_or_surface!(self, id, |w| w.add_font_face(bytes.to_vec(), index), || FontFaceId::INVALID)
}
fn delete_font_face(&mut self, id: WindowId, font_face_id: FontFaceId) {
with_window_or_surface!(self, id, |w| w.delete_font_face(font_face_id), || ())
}
fn add_font(
&mut self,
id: WindowId,
font_face_id: FontFaceId,
glyph_size: Px,
options: FontOptions,
variations: Vec<(FontVariationName, f32)>,
) -> FontId {
with_window_or_surface!(self, id, |w| w.add_font(font_face_id, glyph_size, options, variations), || {
FontId::INVALID
})
}
fn delete_font(&mut self, id: WindowId, font_id: FontId) {
with_window_or_surface!(self, id, |w| w.delete_font(font_id), || ())
}
fn set_capture_mode(&mut self, id: WindowId, enabled: bool) {
self.with_window(id, |w| w.set_capture_mode(enabled), || ())
}
fn frame_image(&mut self, id: WindowId, mask: Option<ImageMaskMode>) -> ImageId {
with_window_or_surface!(self, id, |w| w.frame_image(&mut self.image_cache, mask), || ImageId::INVALID)
}
fn frame_image_rect(&mut self, id: WindowId, rect: PxRect, mask: Option<ImageMaskMode>) -> ImageId {
with_window_or_surface!(self, id, |w| w.frame_image_rect(&mut self.image_cache, rect, mask), || {
ImageId::INVALID
})
}
fn render(&mut self, id: WindowId, frame: FrameRequest) {
with_window_or_surface!(self, id, |w| w.render(frame), || ())
}
fn render_update(&mut self, id: WindowId, frame: FrameUpdateRequest) {
with_window_or_surface!(self, id, |w| w.render_update(frame), || ())
}
fn access_update(&mut self, id: WindowId, update: access::AccessTreeUpdate) {
if let Some(s) = self.windows.iter_mut().find(|s| s.id() == id) {
s.access_update(update, &self.app_sender);
}
}
fn message_dialog(&mut self, id: WindowId, dialog: MsgDialog) -> DialogId {
let r_id = self.dialog_id_gen.incr();
if let Some(s) = self.windows.iter_mut().find(|s| s.id() == id) {
s.message_dialog(dialog, r_id, self.app_sender.clone());
} else {
let r = MsgDialogResponse::Error(Txt::from_static("window not found"));
let _ = self.app_sender.send(AppEvent::Notify(Event::MsgDialogResponse(r_id, r)));
}
r_id
}
fn file_dialog(&mut self, id: WindowId, dialog: FileDialog) -> DialogId {
let r_id = self.dialog_id_gen.incr();
if let Some(s) = self.windows.iter_mut().find(|s| s.id() == id) {
s.file_dialog(dialog, r_id, self.app_sender.clone());
} else {
let r = MsgDialogResponse::Error(Txt::from_static("window not found"));
let _ = self.app_sender.send(AppEvent::Notify(Event::MsgDialogResponse(r_id, r)));
};
r_id
}
#[cfg(windows)]
fn read_clipboard(&mut self, data_type: clipboard::ClipboardType) -> Result<clipboard::ClipboardData, clipboard::ClipboardError> {
match data_type {
clipboard::ClipboardType::Text => {
let _clip = clipboard_win::Clipboard::new_attempts(10).map_err(util::clipboard_win_to_clip)?;
clipboard_win::get(clipboard_win::formats::Unicode)
.map_err(util::clipboard_win_to_clip)
.map(|s: String| clipboard::ClipboardData::Text(Txt::from_str(&s)))
}
clipboard::ClipboardType::Image => {
let _clip = clipboard_win::Clipboard::new_attempts(10).map_err(util::clipboard_win_to_clip)?;
let bitmap = clipboard_win::get(clipboard_win::formats::Bitmap).map_err(util::clipboard_win_to_clip)?;
let id = self.image_cache.add(ImageRequest {
format: image::ImageDataFormat::FileExtension(Txt::from_str("bmp")),
data: IpcBytes::from_vec(bitmap),
max_decoded_len: u64::MAX,
downscale: None,
mask: None,
});
Ok(clipboard::ClipboardData::Image(id))
}
clipboard::ClipboardType::FileList => {
let _clip = clipboard_win::Clipboard::new_attempts(10).map_err(util::clipboard_win_to_clip)?;
clipboard_win::get(clipboard_win::formats::FileList)
.map_err(util::clipboard_win_to_clip)
.map(clipboard::ClipboardData::FileList)
}
clipboard::ClipboardType::Extension(_) => Err(clipboard::ClipboardError::NotSupported),
}
}
#[cfg(windows)]
fn write_clipboard(&mut self, data: clipboard::ClipboardData) -> Result<(), clipboard::ClipboardError> {
use zng_txt::formatx;
match data {
clipboard::ClipboardData::Text(t) => {
let _clip = clipboard_win::Clipboard::new_attempts(10).map_err(util::clipboard_win_to_clip)?;
clipboard_win::set(clipboard_win::formats::Unicode, t).map_err(util::clipboard_win_to_clip)
}
clipboard::ClipboardData::Image(id) => {
let _clip = clipboard_win::Clipboard::new_attempts(10).map_err(util::clipboard_win_to_clip)?;
if let Some(img) = self.image_cache.get(id) {
let mut bmp = vec![];
img.encode(::image::ImageFormat::Bmp, &mut bmp)
.map_err(|e| clipboard::ClipboardError::Other(formatx!("{e:?}")))?;
clipboard_win::set(clipboard_win::formats::Bitmap, bmp).map_err(util::clipboard_win_to_clip)
} else {
Err(clipboard::ClipboardError::Other(Txt::from_str("image not found")))
}
}
clipboard::ClipboardData::FileList(l) => {
use clipboard_win::Setter;
let _clip = clipboard_win::Clipboard::new_attempts(10).map_err(util::clipboard_win_to_clip)?;
let strs = l.into_iter().map(|p| p.display().to_string()).collect::<Vec<String>>();
clipboard_win::formats::FileList
.write_clipboard(&strs)
.map_err(util::clipboard_win_to_clip)
}
clipboard::ClipboardData::Extension { .. } => Err(clipboard::ClipboardError::NotSupported),
}
}
#[cfg(not(any(windows, target_os = "android")))]
fn read_clipboard(&mut self, data_type: clipboard::ClipboardType) -> Result<clipboard::ClipboardData, clipboard::ClipboardError> {
match data_type {
clipboard::ClipboardType::Text => self
.arboard()?
.get_text()
.map_err(util::arboard_to_clip)
.map(|s| clipboard::ClipboardData::Text(zng_txt::Txt::from(s))),
clipboard::ClipboardType::Image => {
let bitmap = self.arboard()?.get_image().map_err(util::arboard_to_clip)?;
let mut data = bitmap.bytes.into_owned();
for rgba in data.chunks_exact_mut(4) {
rgba.swap(0, 2); }
let id = self.image_cache.add(image::ImageRequest {
format: image::ImageDataFormat::Bgra8 {
size: zng_unit::PxSize::new(Px(bitmap.width as _), Px(bitmap.height as _)),
ppi: None,
},
data: IpcBytes::from_vec(data),
max_decoded_len: u64::MAX,
downscale: None,
mask: None,
});
Ok(clipboard::ClipboardData::Image(id))
}
clipboard::ClipboardType::FileList => Err(clipboard::ClipboardError::NotSupported),
clipboard::ClipboardType::Extension(_) => Err(clipboard::ClipboardError::NotSupported),
}
}
#[cfg(not(any(windows, target_os = "android")))]
fn write_clipboard(&mut self, data: clipboard::ClipboardData) -> Result<(), clipboard::ClipboardError> {
match data {
clipboard::ClipboardData::Text(t) => self.arboard()?.set_text(t).map_err(util::arboard_to_clip),
clipboard::ClipboardData::Image(id) => {
self.arboard()?;
if let Some(img) = self.image_cache.get(id) {
let size = img.size();
let mut data = img.pixels().clone().to_vec();
for rgba in data.chunks_exact_mut(4) {
rgba.swap(0, 2); }
let board = self.arboard()?;
let _ = board.set_image(arboard::ImageData {
width: size.width.0 as _,
height: size.height.0 as _,
bytes: std::borrow::Cow::Owned(data),
});
Ok(())
} else {
Err(clipboard::ClipboardError::Other(zng_txt::Txt::from_static("image not found")))
}
}
clipboard::ClipboardData::FileList(_) => Err(clipboard::ClipboardError::NotSupported),
clipboard::ClipboardData::Extension { .. } => Err(clipboard::ClipboardError::NotSupported),
}
}
#[cfg(target_os = "android")]
fn read_clipboard(&mut self, data_type: clipboard::ClipboardType) -> Result<clipboard::ClipboardData, clipboard::ClipboardError> {
let _ = data_type;
Err(clipboard::ClipboardError::Other(Txt::from_static(
"clipboard not implemented for Android",
)))
}
#[cfg(target_os = "android")]
fn write_clipboard(&mut self, data: clipboard::ClipboardData) -> Result<(), clipboard::ClipboardError> {
let _ = data;
Err(clipboard::ClipboardError::Other(Txt::from_static(
"clipboard not implemented for Android",
)))
}
fn set_system_shutdown_warn(&mut self, id: WindowId, reason: Txt) {
self.with_window(id, move |w| w.set_system_shutdown_warn(reason), || ())
}
fn third_party_licenses(&mut self) -> Vec<zng_tp_licenses::LicenseUsed> {
#[cfg(feature = "bundle_licenses")]
{
zng_tp_licenses::include_bundle!()
}
#[cfg(not(feature = "bundle_licenses"))]
{
vec![]
}
}
fn app_extension(&mut self, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> ApiExtensionPayload {
self.exts.call_command(extension_id, extension_request)
}
fn window_extension(
&mut self,
id: WindowId,
extension_id: ApiExtensionId,
extension_request: ApiExtensionPayload,
) -> ApiExtensionPayload {
self.with_window(
id,
|w| w.window_extension(extension_id, extension_request),
|| ApiExtensionPayload::invalid_request(extension_id, "window not found"),
)
}
fn render_extension(
&mut self,
id: WindowId,
extension_id: ApiExtensionId,
extension_request: ApiExtensionPayload,
) -> ApiExtensionPayload {
with_window_or_surface!(self, id, |w| w.render_extension(extension_id, extension_request), || {
ApiExtensionPayload::invalid_request(extension_id, "renderer not found")
})
}
}
#[derive(Debug)]
pub(crate) enum AppEvent {
Request,
Notify(Event),
#[cfg_attr(not(windows), allow(unused))]
RefreshMonitors,
#[cfg_attr(not(windows), allow(unused))]
WinitFocused(winit::window::WindowId, bool),
ParentProcessExited,
ImageLoaded(ImageLoadedData),
InitDeviceEvents(bool),
#[allow(unused)]
MonitorPowerChanged,
}
#[derive(Debug)]
enum RequestEvent {
Request(Request),
FrameReady(WindowId, FrameReadyMsg),
}
#[derive(Debug)]
pub(crate) struct FrameReadyMsg {
pub composite_needed: bool,
}
#[derive(Clone)]
pub(crate) enum AppEventSender {
Headed(EventLoopProxy<AppEvent>, flume::Sender<RequestEvent>),
Headless(flume::Sender<AppEvent>, flume::Sender<RequestEvent>),
}
impl AppEventSender {
fn send(&self, ev: AppEvent) -> Result<(), ipc::Disconnected> {
match self {
AppEventSender::Headed(p, _) => p.send_event(ev).map_err(|_| ipc::Disconnected),
AppEventSender::Headless(p, _) => p.send(ev).map_err(|_| ipc::Disconnected),
}
}
fn request(&self, req: Request) -> Result<(), ipc::Disconnected> {
match self {
AppEventSender::Headed(_, p) => p.send(RequestEvent::Request(req)).map_err(|_| ipc::Disconnected),
AppEventSender::Headless(_, p) => p.send(RequestEvent::Request(req)).map_err(|_| ipc::Disconnected),
}?;
self.send(AppEvent::Request)
}
fn frame_ready(&self, window_id: WindowId, msg: FrameReadyMsg) -> Result<(), ipc::Disconnected> {
match self {
AppEventSender::Headed(_, p) => p.send(RequestEvent::FrameReady(window_id, msg)).map_err(|_| ipc::Disconnected),
AppEventSender::Headless(_, p) => p.send(RequestEvent::FrameReady(window_id, msg)).map_err(|_| ipc::Disconnected),
}?;
self.send(AppEvent::Request)
}
}
pub(crate) struct WrNotifier {
id: WindowId,
sender: AppEventSender,
}
impl WrNotifier {
pub fn create(id: WindowId, sender: AppEventSender) -> Box<dyn RenderNotifier> {
Box::new(WrNotifier { id, sender })
}
}
impl RenderNotifier for WrNotifier {
fn clone(&self) -> Box<dyn RenderNotifier> {
Box::new(Self {
id: self.id,
sender: self.sender.clone(),
})
}
fn wake_up(&self, _: bool) {}
fn new_frame_ready(&self, _document_id: DocumentId, _scrolled: bool, composite_needed: bool, _: FramePublishId) {
let msg = FrameReadyMsg { composite_needed };
let _ = self.sender.frame_ready(self.id, msg);
}
}
#[cfg(target_arch = "wasm32")]
compile_error!("zng-view does not support Wasm");