1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
11#![warn(unused_extern_crates)]
12#![warn(missing_docs)]
13
14use core::fmt;
15use libloading::*;
16use parking_lot::Mutex;
17use std::{env, io, mem, path::PathBuf};
18use zng_view_api::StaticPatch;
19
20zng_env::on_process_start!(|_| {
21 if std::env::var("ZNG_VIEW_NO_INIT_START").is_err() {
22 if !zng_env::about().is_test {
23 view_process_main();
24 } else {
25 tracing::debug!("view-process not inited in test app");
26 }
27 }
28});
29
30pub fn view_process_main() {
40 ViewLib::install().unwrap().view_process_main()
41}
42
43pub fn run_same_process(run_app: impl FnOnce() + Send + 'static) -> ! {
59 ViewLib::install().unwrap().run_same_process(run_app)
60}
61
62pub struct ViewLib {
64 view_process_main_fn: unsafe extern "C" fn(&StaticPatch),
65 run_same_process_fn: unsafe extern "C" fn(&StaticPatch, extern "C" fn()),
66 _lib: Library,
67}
68impl ViewLib {
69 pub fn install() -> Result<Self, Error> {
71 let dir = env::temp_dir().join("zng_view");
72 std::fs::create_dir_all(&dir)?;
73 Self::install_to(dir)
74 }
75
76 pub fn uninstall() -> Result<bool, io::Error> {
82 let dir = env::temp_dir().join("zng_view");
83 Self::uninstall_from(dir)
84 }
85
86 pub fn install_to(dir: impl Into<PathBuf>) -> Result<Self, Error> {
88 Self::install_to_impl(dir.into())
89 }
90 fn install_to_impl(dir: PathBuf) -> Result<Self, Error> {
91 #[cfg(not(zng_lib_embedded))]
92 {
93 let _ = dir;
94 panic!("library not embedded");
95 }
96
97 #[cfg(zng_lib_embedded)]
98 {
99 let file = Self::install_path(dir);
100
101 if !file.exists() {
102 std::fs::write(&file, LIB)?;
103 }
104
105 Self::link(file)
106 }
107 }
108
109 pub fn uninstall_from(dir: impl Into<PathBuf>) -> Result<bool, io::Error> {
117 Self::uninstall_from_impl(dir.into())
118 }
119 fn uninstall_from_impl(dir: PathBuf) -> Result<bool, io::Error> {
120 #[cfg(not(zng_lib_embedded))]
121 {
122 let _ = dir;
123 Ok(false)
124 }
125
126 #[cfg(zng_lib_embedded)]
127 {
128 let file = Self::install_path(dir);
129
130 if file.exists() {
131 std::fs::remove_file(file)?;
132 Ok(true)
133 } else {
134 Ok(false)
135 }
136 }
137 }
138
139 #[cfg(zng_lib_embedded)]
140 fn install_path(dir: PathBuf) -> PathBuf {
141 #[cfg(target_os = "windows")]
142 let file_name = format!("{LIB_NAME}.dll");
143 #[cfg(target_os = "linux")]
144 let file_name = format!("{LIB_NAME}.so");
145 #[cfg(target_os = "macos")]
146 let file_name = format!("{LIB_NAME}.dylib");
147
148 dir.join(file_name)
149 }
150
151 pub fn link(view_dylib: impl Into<PathBuf>) -> Result<Self, Error> {
159 Self::link_impl(view_dylib.into())
160 }
161 fn link_impl(mut lib: PathBuf) -> Result<Self, Error> {
162 if !lib.exists() && lib.extension().is_none() {
163 #[cfg(target_os = "windows")]
164 lib.set_extension("dll");
165 #[cfg(target_os = "linux")]
166 lib.set_extension("so");
167 #[cfg(target_os = "macos")]
168 lib.set_extension("dylib");
169 }
170
171 if lib.exists() {
172 lib = dunce::canonicalize(lib)?;
174 }
175
176 if !lib.exists() {
177 return Err(io::Error::new(io::ErrorKind::NotFound, format!("view library not found in `{}`", lib.display())).into());
178 }
179
180 unsafe {
181 let lib = Library::new(lib)?;
182 Ok(ViewLib {
183 view_process_main_fn: *match lib.get(b"extern_view_process_main") {
184 Ok(f) => f,
185 Err(e) => match lib.get(b"extern_init") {
187 Ok(f) => f,
188 Err(_) => return Err(e.into()),
189 },
190 },
191 run_same_process_fn: *lib.get(b"extern_run_same_process")?,
192 _lib: lib,
193 })
194 }
195 }
196
197 pub fn view_process_main(self) {
206 let patch = StaticPatch::capture();
207 unsafe { (self.view_process_main_fn)(&patch) }
208 }
209
210 pub fn run_same_process(self, run_app: impl FnOnce() + Send + 'static) -> ! {
221 self.run_same_process_impl(Box::new(run_app))
222 }
223 fn run_same_process_impl(self, run_app: Box<dyn FnOnce() + Send>) -> ! {
224 let patch = StaticPatch::capture();
225
226 enum Run {
227 Waiting,
228 Set(Box<dyn FnOnce() + Send>),
229 Taken,
230 }
231 static RUN: Mutex<Run> = Mutex::new(Run::Waiting);
232
233 match mem::replace(&mut *RUN.lock(), Run::Set(Box::new(run_app))) {
234 Run::Waiting => {}
235 _ => panic!("expected only one call to `run_same_process`"),
236 };
237
238 extern "C" fn run() {
239 match mem::replace(&mut *RUN.lock(), Run::Taken) {
240 Run::Set(run_app) => run_app(),
241 _ => unreachable!(),
242 }
243 }
244
245 unsafe {
247 (self.run_same_process_fn)(&patch, run);
248 }
249
250 zng_env::exit(0)
252 }
253}
254
255#[cfg(zng_lib_embedded)]
256const LIB: &[u8] = include_bytes!(env!("ZNG_VIEW_LIB"));
257#[cfg(zng_lib_embedded)]
258const LIB_NAME: &str = concat!("zv.", env!("CARGO_PKG_VERSION"), ".", env!("ZNG_VIEW_LIB_HASH"));
259
260#[derive(Debug)]
262#[non_exhaustive]
263pub enum Error {
264 Io(io::Error),
266 Lib(libloading::Error),
268}
269impl From<io::Error> for Error {
270 fn from(e: io::Error) -> Self {
271 Error::Io(e)
272 }
273}
274impl From<libloading::Error> for Error {
275 fn from(e: libloading::Error) -> Self {
276 Error::Lib(e)
277 }
278}
279impl fmt::Display for Error {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 match self {
282 Error::Io(e) => write!(f, "{e}"),
283 Error::Lib(e) => write!(f, "{e}"),
284 }
285 }
286}
287impl std::error::Error for Error {
288 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
289 match self {
290 Error::Io(e) => Some(e),
291 Error::Lib(e) => Some(e),
292 }
293 }
294}