Repository: zng-ui/zng (language: rust)

Issue:
Implement a `process_entry!()` switch to better coordnate the app processes
The app executable can spawn multiple processes, and to support Windows they can't be forks, so we have these functions that must be called at the `main` entry that takeover process execution.

We could use the `linkme` crate to create a new `process_entry!()` that is a single thing for users to call, the compiled process spawners register a callback that just does the env check to takeover the process.

Potential issue, `linkme` declares an item, can it be declared in the fn main  scope?


Discussion / hints:
Will unify with `zng::env::init!`

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 80d8eea7d..2faea213c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Unpublished
 
+* Add `zng_env::process_main!` to inject a "process takeover" callback in `zng::env::init!`,
+* **Breaking** Remove `zng_view::init`, `zng_view_prebuilt::init`, `zng::view_process::default::init`, `zng::view_process::prebuilt::init`.
+    - Only `zng::env::init!()` needs to be called to setup the view-process.
+* **Breaking** `zng_view_api::Controller::start` now requires the exe path and supports optional env variables to set.
+    - This only affects custom view-process implementers.
+
 
 # 0.6.2
 
diff --git a/README.md b/README.md
index 0e120bdbf..45b7e892c 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ Then create your first window:
 use zng::prelude::*;
 
 fn main() {
-    zng::view_process::prebuilt::init();
+    zng::env::init!();
     APP.defaults().run_window(async {
         let size = var(layout::Size::new(800, 600));
         Window! {
diff --git a/crates/zng-app/src/lib.rs b/crates/zng-app/src/lib.rs
index 499b0dbc8..5a8bf513d 100644
--- a/crates/zng-app/src/lib.rs
+++ b/crates/zng-app/src/lib.rs
@@ -18,6 +18,7 @@
 
 use std::{
     any::{type_name, TypeId},
+    collections::HashMap,
     fmt,
     future::Future,
     ops,
@@ -1186,6 +1187,7 @@ impl APP {
         AppExtended {
             extensions: vec![],
             view_process_exe: None,
+            view_process_env: HashMap::new(),
             _cleanup: scope,
         }
     }
@@ -1202,6 +1204,7 @@ impl APP {
         AppExtended {
             extensions: (),
             view_process_exe: None,
+            view_process_env: HashMap::new(),
             _cleanup: scope,
         }
     }
@@ -1213,6 +1216,7 @@ impl APP {
 pub struct AppExtended<E: AppExtension> {
     extensions: E,
     view_process_exe: Option<PathBuf>,
+    view_process_env: HashMap<Txt, Txt>,
 
     // cleanup on drop.
     _cleanup: AppScope,
@@ -1240,21 +1244,29 @@ impl AppExtended<Vec<Box<dyn AppExtensionBoxed>>> {
         self.extend(EnableDeviceEvents)
     }
 
-    fn run_dyn(mut self, start: std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>) {
-        let app = RunningApp::start(self._cleanup, self.extensions, true, true, self.view_process_exe.take());
+    fn run_dyn(self, start: std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>) {
+        let app = RunningApp::start(
+            self._cleanup,
+            self.extensions,
+            true,
+            true,
+            self.view_process_exe,
+            self.view_process_env,
+        );
 
         UPDATES.run(start).perm();
 
         app.run_headed();
     }
 
-    fn run_headless_dyn(mut self, with_renderer: bool) -> HeadlessApp {
+    fn run_headless_dyn(self, with_renderer: bool) -> HeadlessApp {
         let app = RunningApp::start(
             self._cleanup,
             self.extensions.boxed(),
             false,
             with_renderer,
-            self.view_process_exe.take(),
+            self.view_process_exe,
+            self.view_process_env,
         );
 
         HeadlessApp { app }
@@ -1273,6 +1285,7 @@ impl<E: AppExtension> AppExtended<E> {
                 AppExtended {
                     extensions: vec![app.extensions.boxed()],
                     view_process_exe: app.view_process_exe,
+                    view_process_env: app.view_process_env,
                     _cleanup: app._cleanup,
                 }
             }
@@ -1296,6 +1309,7 @@ impl<E: AppExtension> AppExtended<E> {
             _cleanup: self._cleanup,
             extensions: (self.extensions, TraceAppExt(extension)),
             view_process_exe: self.view_process_exe,
+            view_process_env: self.view_process_env,
         }
     }
 
@@ -1314,21 +1328,29 @@ impl<E: AppExtension> AppExtended<E> {
         self.extend(EnableDeviceEvents)
     }
 
-    fn run_impl(mut self, start: impl Future<Output = ()> + Send + 'static) {
-        let app = RunningApp::start(self._cleanup, self.extensions, true, true, self.view_process_exe.take());
+    fn run_impl(self, start: impl Future<Output = ()> + Send + 'static) {
+        let app = RunningApp::start(
+            self._cleanup,
+            self.extensions,
+            true,
+            true,
+            self.view_process_exe,
+            self.view_process_env,
+        );
 
         UPDATES.run(start).perm();
 
         app.run_headed();
     }
 
-    fn run_headless_impl(mut self, with_renderer: bool) -> HeadlessApp {
+    fn run_headless_impl(self, with_renderer: bool) -> HeadlessApp {
         let app = RunningApp::start(
             self._cleanup,
             self.extensions.boxed(),
             false,
             with_renderer,
-            self.view_process_exe.take(),
+            self.view_process_exe,
+            self.view_process_env,
         );
 
         HeadlessApp { app }
@@ -1349,6 +1371,12 @@ impl<E: AppExtension> AppExtended<E> {
         self
     }
 
+    /// Set an env variable for the view-process.
+    pub fn view_process_env(mut self, name: impl Into<Txt>, value: impl Into<Txt>) -> Self {
+        self.view_process_env.insert(name.into(), value.into());
+        self
+    }
+
     /// Starts the app, then starts polling `start` to run.
     ///
     /// This method only returns when the app has exited.
diff --git a/crates/zng-app/src/running.rs b/crates/zng-app/src/running.rs
index fc6e7d89e..95be0132a 100644
--- a/crates/zng-app/src/running.rs
+++ b/crates/zng-app/src/running.rs
@@ -1,4 +1,5 @@
 use std::{
+    collections::HashMap,
     fmt, mem,
     path::PathBuf,
     sync::Arc,
@@ -10,6 +11,7 @@ use crate::Deadline;
 use zng_app_context::{app_local, AppScope};
 use zng_task::DEADLINE_APP;
 use zng_time::{InstantMode, INSTANT_APP};
+use zng_txt::Txt;
 use zng_var::{response_var, ArcVar, ResponderVar, ResponseVar, Var as _, VARS, VARS_APP};
 
 use crate::{
@@ -55,6 +57,7 @@ impl<E: AppExtension> RunningApp<E> {
         is_headed: bool,
         with_renderer: bool,
         view_process_exe: Option<PathBuf>,
+        view_process_env: HashMap<Txt, Txt>,
     ) -> Self {
         let _s = tracing::debug_span!("APP::start").entered();
 
@@ -82,7 +85,9 @@ impl<E: AppExtension> RunningApp<E> {
             sv.set_extensions(info, device_events);
         }
 
-        let process = AppIntrinsic::pre_init(is_headed, with_renderer, view_process_exe, device_events);
+        let view_process_exe = view_process_exe.unwrap_or_else(|| std::env::current_exe().expect("current_exe"));
+
+        let process = AppIntrinsic::pre_init(is_headed, with_renderer, view_process_exe, view_process_env, device_events);
 
         {
             let _s = tracing::debug_span!("extensions.init").entered();
@@ -1136,7 +1141,13 @@ struct PendingExit {
 }
 impl AppIntrinsic {
     /// Pre-init intrinsic services and commands, must be called before extensions init.
-    pub(super) fn pre_init(is_headed: bool, with_renderer: bool, view_process_exe: Option<PathBuf>, device_events: bool) -> Self {
+    pub(super) fn pre_init(
+        is_headed: bool,
+        with_renderer: bool,
+        view_process_exe: PathBuf,
+        view_process_env: HashMap<Txt, Txt>,
+        device_events: bool,
+    ) -> Self {
         APP_PROCESS_SV
             .read()
             .pause_time_for_updates
@@ -1156,12 +1167,12 @@ impl AppIntrinsic {
             debug_assert!(with_renderer);
 
             let view_evs_sender = UPDATES.sender();
-            VIEW_PROCESS.start(view_process_exe, device_events, false, move |ev| {
+            VIEW_PROCESS.start(view_process_exe, view_process_env, device_events, false, move |ev| {
                 let _ = view_evs_sender.send_view_event(ev);
             });
         } else if with_renderer {
             let view_evs_sender = UPDATES.sender();
-            VIEW_PROCESS.start(view_process_exe, false, true, move |ev| {
+            VIEW_PROCESS.start(view_process_exe, view_process_env, false, true, move |ev| {
                 let _ = view_evs_sender.send_view_event(ev);
             });
         }
diff --git a/crates/zng-app/src/view_process.rs b/crates/zng-app/src/view_process.rs
index cf37e2a06..4651db1c3 100644
--- a/crates/zng-app/src/view_process.rs
+++ b/crates/zng-app/src/view_process.rs
@@ -297,13 +297,19 @@ impl VIEW_PROCESS {
     }
 
     /// Spawn the View Process.
-    pub(super) fn start<F>(&self, view_process_exe: Option<PathBuf>, device_events: bool, headless: bool, on_event: F)
-    where
+    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, device_events, headless, on_event);
+        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,
diff --git a/crates/zng-env-proc-macros/Cargo.toml b/crates/zng-env-proc-macros/Cargo.toml
index 0f0d1c62b..262662fa3 100644
--- a/crates/zng-env-proc-macros/Cargo.toml
+++ b/crates/zng-env-proc-macros/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2021"
 license = "Apache-2.0 OR MIT"
 readme = "README.md"
 description = "Part of the zng project."
-documentation = "https://zng-ui.github.io/doc/zng_color_proc_macros"
+documentation = "https://zng-ui.github.io/doc/zng_env_proc_macros"
 repository = "https://github.com/zng-ui/zng"
 categories = ["gui"]
 keywords = ["gui", "ui", "user-interface", "zng"]
diff --git a/crates/zng-env-proc-macros/src/lib.rs b/crates/zng-env-proc-macros/src/lib.rs
index e32b1ac6e..ea96dd1cf 100644
--- a/crates/zng-env-proc-macros/src/lib.rs
+++ b/crates/zng-env-proc-macros/src/lib.rs
@@ -46,7 +46,7 @@ pub fn init_parse(crate_: TokenStream) -> TokenStream {
     };
     let p_name = m.package.name;
     let c_name = p_name.replace('-', "_");
-    let p_authors = m.package.authors;
+    let p_authors = m.package.authors.unwrap_or_default();
     let major = m.package.version.major;
     let minor = m.package.version.minor;
     let patch = m.package.version.patch;
@@ -120,7 +120,7 @@ struct Package {
     version: Version,
     description: Option<String>,
     homepage: Option<String>,
-    authors: Box<[String]>,
+    authors: Option<Box<[String]>>,
     metadata: Option<Metadata>,
 }
 #[derive(serde::Deserialize)]
diff --git a/crates/zng-env/Cargo.toml b/crates/zng-env/Cargo.toml
index 5b08ffc9a..4feb2996b 100644
--- a/crates/zng-env/Cargo.toml
+++ b/crates/zng-env/Cargo.toml
@@ -25,6 +25,7 @@ tracing = "0.1"
 toml = "0.8"
 serde = { version = "1.0", features = ["derive"] }
 semver = { version = "1.0", features = ["serde"] }
+linkme = "0.3"
 
 zng-unique-id = { path = "../zng-unique-id", version = "0.4.2" }
 zng-txt = { path = "../zng-txt", version = "0.2.6" }
diff --git a/crates/zng-env/src/lib.rs b/crates/zng-env/src/lib.rs
index ddc483b56..f7416942f 100644
--- a/crates/zng-env/src/lib.rs
+++ b/crates/zng-env/src/lib.rs
@@ -10,7 +10,7 @@
 #![warn(missing_docs)]
 
 use std::{
-    fs,
+    env, fs,
     io::{self, BufRead},
     path::{Path, PathBuf},
     str::FromStr,
@@ -24,9 +24,15 @@ lazy_static! {
     static ref ABOUT: About = About::fallback_name();
 }
 
-/// Init [`about`] from cargo manifest.
+/// Init [`about`] from cargo manifest and handles witch process to execute.
 ///
 /// See [`About`] docs for what Cargo.toml values are read.
+///
+/// # Processes
+///
+/// A single Zng executable can be built with multiple components that spawn different instances
+/// of the executable that must run as different processes. If the current instance is requested
+/// by component `init!` runs it and exits the process, never returning flow to the normal main function.
 #[macro_export]
 macro_rules! init {
     () => {
@@ -36,16 +42,67 @@ macro_rules! init {
 #[doc(hidden)]
 pub use zng_env_proc_macros::init_parse;
 
-/// Initialize [`about`] manually.
+/// Register a `fn() -> !` pointer to be called on [`init!`].
+///
+/// Components that spawn special process instances implemented on the same executable
+/// can use this macro to inject their own "main" without needing to ask the user to plug an init
+/// function on the executable main. The component can set the [`PROCESS_MAIN`] env var to spawn an
+/// instance of the executable that run as the component's process.
+///
+/// # Examples
+///
+/// First add dependency to [`linkme`](https://crates.io/crates/linkme) in Cargo.toml.
+///
+/// Then declare the "main" entry for the process:
+///
+/// ```
+/// zng_env::process_main!("my-crate/foo-process" => foo_main);
+/// fn foo_main() -> ! {
+///     println!("Spawned as foo!");
+///     std::process::exit(0)
+/// }
 ///
-/// Prefer calling [`init!`] instead of this, or at least also set duplicate values in `[package.metadata.zng.about]`
-/// on the Cargo.toml manifest. Cargo tools like `cargo zng res` parse these metadata when packaging resources.
+/// fn main() {
+///     zng_env::init!(); // foo_main OR
+///     // normal main
+/// }
+///
+/// pub fn spawn_foo() -> std::io::Result<()> {
+///     std::process::Command::new(std::env::current_exe()?).env(zng_env::PROCESS_MAIN, "my-crate/foo-process").spawn()?;
+///     Ok(())
+/// }
+/// ```
+///
+/// The example above declares a "main" for a foo component and a function that spawns it.
+#[macro_export]
+macro_rules! process_main {
+    ($name:tt => $init_fn:path) => {
+        #[doc(hidden)]
+        #[::linkme::distributed_slice($crate::ZNG_ENV_RUN_PROCESS)]
+        static _ZNG_ENV_RUN_PROCESS: $crate::RunAsProcessHandler = $crate::RunAsProcessHandler::new($name, $init_fn);
+    };
+}
+
+#[doc(hidden)]
 pub fn init(about: About) {
     if lazy_static_init(&ABOUT, about).is_err() {
         panic!("env already inited, env::init must be the first call in the process")
     }
+    if let Ok(name) = env::var(PROCESS_MAIN) {
+        if !name.is_empty() {
+            for h in ZNG_ENV_RUN_PROCESS {
+                if h.name == name {
+                    (h.handler)() // -> !
+                }
+            }
+            panic!("{PROCESS_MAIN}={name:?} is not registered with process_main!");
+        }
+    }
 }
 
+/// Env var key that must be set to the [`process_main!`] name to run that process.
+pub const PROCESS_MAIN: &str = "ZNG_ENV_PROCESS_MAIN";
+
 /// Metadata about the app and main crate.
 ///
 /// See [`about`] for more details.
@@ -702,6 +759,21 @@ fn read_line(path: &Path) -> io::Result<String> {
     Err(io::Error::new(io::ErrorKind::UnexpectedEof, "no uncommented line"))
 }
 
+#[doc(hidden)]
+#[linkme::distributed_slice]
+pub static ZNG_ENV_RUN_PROCESS: [RunAsProcessHandler];
+
+#[doc(hidden)]
+pub struct RunAsProcessHandler {
+    name: &'static str,
+    handler: fn() -> !,
+}
+impl RunAsProcessHandler {
+    pub const fn new(name: &'static str, handler: fn() -> !) -> Self {
+        Self { name, handler }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::*;
diff --git a/crates/zng-ext-single-instance/src/lib.rs b/crates/zng-ext-single-instance/src/lib.rs
index d18020881..b08498b47 100644
--- a/crates/zng-ext-single-instance/src/lib.rs
+++ b/crates/zng-ext-single-instance/src/lib.rs
@@ -146,47 +146,6 @@ event! {
 /// the app starts building. After the app starts building, before run, a subscription for [`APP_INSTANCE_EVENT`] is setup,
 /// this event will receive args for the current instance on run and for other instances latter.
 ///
-/// ```
-/// # mod zng { pub(crate) use zng_ext_single_instance as app; }
-/// # mod view_process { pub fn init() { } }
-/// # trait FakeDefaults { fn defaults(self) -> zng_app::AppExtended<impl zng_app::AppExtension>; }
-/// # impl FakeDefaults for zng_app::APP { fn defaults(self) -> zng_app::AppExtended<impl zng_app::AppExtension> { self.minimal() } }
-/// # use zng_app::{APP, handler::app_hn};
-/// fn main() {
-///     view_process::init();
-///     // zng::task::ipc::run_worker(worker);
-///
-///     // must be called after `view_process`, `run_worker` and before the APP build.
-///     zng::app::single_instance();
-///
-///     app_main();
-/// }
-///
-/// fn app_main() {
-///     let app = APP.defaults();
-///
-///     zng::app::APP_INSTANCE_EVENT
-///         .on_event(app_hn!(|args: &zng::app::AppInstanceArgs, _| {
-///             println!("app instance #{}, args: {:?}", args.count, args.args);
-///         }))
-///         .perm();
-///
-/// # macro_rules! demo { () => {
-///     app.run_window(async {
-///         Window! {
-///             child_align = Align::CENTER;
-///             child = Button! {
-///                 child = Text!("Spawn Instance");
-///                 on_click = hn!(|_| {
-///                     let exe = std::env::current_exe().unwrap();
-///                     std::process::Command::new(exe).arg("app arg 1").arg("--arg2").spawn().unwrap();
-///                 });
-///             }
-///         }
-///     });
-/// # }}
-/// }
-/// ```
 pub fn single_instance() {
     single_instance_named(
         std::env::current_exe()
diff --git a/crates/zng-task/Cargo.toml b/crates/zng-task/Cargo.toml
index 1c5281c54..e3794acb8 100644
--- a/crates/zng-task/Cargo.toml
+++ b/crates/zng-task/Cargo.toml
@@ -53,6 +53,7 @@ zng-unit = { path = "../zng-unit", version = "0.2.6" }
 zng-app-context = { path = "../zng-app-context", version = "0.4.1" }
 zng-time = { path = "../zng-time", version = "0.2.9" }
 zng-var = { path = "../zng-var", version = "0.4.1" }
+zng-env = { path = "../zng-env", version = "0.1.1" }
 
 zng-unique-id = { path = "../zng-unique-id", version = "0.4.2", default-features = false, optional = true }
 zng-txt = { path = "../zng-txt", version = "0.2.6", optional = true }
diff --git a/crates/zng-task/src/ipc.rs b/crates/zng-task/src/ipc.rs
index 54fc9cfaf..97fc9bcc8 100644
--- a/crates/zng-task/src/ipc.rs
+++ b/crates/zng-task/src/ipc.rs
@@ -386,6 +386,11 @@ impl<I: IpcValue, O: IpcValue> Drop for Worker<I, O> {
     }
 }
 
+// !!: TODO
+// fn worker_main() {
+//     zng_env::process_main!(std::concat!("zng-task::ipc/", ""));
+// }
+
 /// If the process was started by a [`Worker`] runs the worker loop and never returns. If
 /// not started as worker does nothing.
 ///
diff --git a/crates/zng-view-api/src/app_process.rs b/crates/zng-view-api/src/app_process.rs
index e7876d5e2..406b31aa4 100644
--- a/crates/zng-view-api/src/app_process.rs
+++ b/crates/zng-view-api/src/app_process.rs
@@ -1,4 +1,5 @@
 use std::{
+    collections::HashMap,
     panic,
     path::{Path, PathBuf},
     thread::{self, JoinHandle},
@@ -40,6 +41,7 @@ pub struct Controller {
     generation: ViewProcessGen,
     is_respawn: bool,
     view_process_exe: PathBuf,
+    view_process_env: HashMap<Txt, Txt>,
     request_sender: ipc::RequestSender,
     response_receiver: ipc::ResponseReceiver,
     event_listener: Option<EventListenerJoin>,
@@ -56,8 +58,11 @@ fn _assert_sync(x: Controller) -> impl Send + Sync {
 impl Controller {
     /// Start with a custom view process.
     ///
-    /// The `view_process_exe` must be an executable that starts a view server, if not set
-    /// the [`current_exe`] is used. Note that the [`VERSION`] of this crate must match in both executables.
+    /// The `view_process_exe` must be an executable that starts a view server.
+    /// Note that the [`VERSION`] of this crate must match in both executables.
+    ///
+    /// The `view_process_env` can be set to any env var needed to start the view-process. Note that if `view_process_exe`
+    /// is the current executable this most likely need set `zng_env::PROCESS_MAIN`.
     ///
     /// The `on_event` closure is called in another thread every time the app receives an event.
     ///
@@ -69,14 +74,21 @@ impl Controller {
     ///
     /// [`current_exe`]: std::env::current_exe
     /// [`VERSION`]: crate::VERSION
-    pub fn start<F>(view_process_exe: Option<PathBuf>, device_events: bool, headless: bool, on_event: F) -> Self
+    pub fn start<F>(
+        view_process_exe: PathBuf,
+        view_process_env: HashMap<Txt, Txt>,
+        device_events: bool,
+        headless: bool,
+        on_event: F,
+    ) -> Self
     where
         F: FnMut(Event) + Send + 'static,
     {
-        Self::start_impl(view_process_exe, device_events, headless, Box::new(on_event))
+        Self::start_impl(view_process_exe, view_process_env, device_events, headless, Box::new(on_event))
     }
     fn start_impl(
-        view_process_exe: Option<PathBuf>,
+        view_process_exe: PathBuf,
+        view_process_env: HashMap<Txt, Txt>,
         device_events: bool,
         headless: bool,
         mut on_event: Box<dyn FnMut(Event) + Send>,
@@ -85,14 +97,8 @@ impl Controller {
             panic!("cannot start Controller in process configured to be view-process");
         }
 
-        let view_process_exe = view_process_exe.unwrap_or_else(|| {
-            std::env::current_exe()
-                .and_then(dunce::canonicalize)
-                .expect("failed to get the current exe")
-        });
-
         let (process, request_sender, response_receiver, mut event_receiver) =
-            Self::spawn_view_process(&view_process_exe, headless).expect("failed to spawn or connect to view-process");
+            Self::spawn_view_process(&view_process_exe, &view_process_env, headless).expect("failed to spawn or connect to view-process");
 
         let ev = thread::spawn(move || {
             while let Ok(ev) = event_receiver.recv() {
@@ -109,6 +115,7 @@ impl Controller {
             online: false,
             process,
             view_process_exe,
+            view_process_env,
             request_sender,
             response_receiver,
             event_listener: Some(ev),
@@ -203,6 +210,7 @@ impl Controller {
 
     fn spawn_view_process(
         view_process_exe: &Path,
+        view_process_env: &HashMap<Txt, Txt>,
         headless: bool,
     ) -> AnyResult<(Option<DuctHandle>, ipc::RequestSender, ipc::ResponseReceiver, ipc::EventReceiver)> {
         let _span = tracing::trace_span!("spawn_view_process").entered();
@@ -220,13 +228,17 @@ impl Controller {
         } else {
             #[cfg(not(feature = "ipc"))]
             {
-                let _ = view_process_exe;
+                let _ = (view_process_exe, view_process_env);
                 panic!("expected only same_process mode with `ipc` feature disabled");
             }
 
             #[cfg(feature = "ipc")]
             {
-                let process = duct::cmd!(view_process_exe)
+                let mut process = duct::cmd!(view_process_exe);
+                for (name, val) in view_process_env {
+                    process = process.env(name, val);
+                }
+                let process = process
                     .env(VIEW_VERSION, crate::VERSION)
                     .env(VIEW_SERVER, init.name())
                     .env(VIEW_MODE, if headless { "headless" } else { "headed" })
@@ -466,7 +478,7 @@ impl Controller {
         // respawn
         let mut retries = 3;
         let (new_process, request, response, mut event) = loop {
-            match Self::spawn_view_process(&self.view_process_exe, self.headless) {
+            match Self::spawn_view_process(&self.view_process_exe, &self.view_process_env, self.headless) {
                 Ok(r) => break r,
                 Err(e) => {
                     tracing::error!(target: "vp_respawn", "failed to respawn, {e:?}");
diff --git a/crates/zng-view-prebuilt/Cargo.toml b/crates/zng-view-prebuilt/Cargo.toml
index 006ef9863..8275a337e 100644
--- a/crates/zng-view-prebuilt/Cargo.toml
+++ b/crates/zng-view-prebuilt/Cargo.toml
@@ -18,8 +18,10 @@ default = ["embedded"]
 embedded = []
 
 [dependencies]
+zng-env = { path = "../zng-env", version = "0.1.1" }
 libloading = "0.8"
 dunce = "1.0"
+linkme = "0.3"
 
 [build-dependencies]
 hashers = "1.0"
diff --git a/crates/zng-view-prebuilt/src/lib.rs b/crates/zng-view-prebuilt/src/lib.rs
index bc78c7058..88ded0a04 100644
--- a/crates/zng-view-prebuilt/src/lib.rs
+++ b/crates/zng-view-prebuilt/src/lib.rs
@@ -15,21 +15,14 @@ use core::fmt;
 use libloading::*;
 use std::{env, io, path::PathBuf};
 
-/// Call the pre-built [`init`].
-///
-/// [`init`]: https://docs.rs/zng-view/fn.init.html
-///
-/// # Panics
-///
-/// Panics if fails to [install] the prebuilt binary.
-///
-/// # Aborts
-///
-/// Kills the process with code `101` if there is a panic generated by the pre-built code or by threads started by the pre-build code.
-///
-/// [install]: ViewLib::install
-pub fn init() {
-    ViewLib::install().unwrap().init()
+/// Name of the view-process in [`zng_env::process_main!`].
+pub const ZNG_ENV_VIEW_PROCESS: &str = "zng-view-prebuilt/view-process";
+
+zng_env::process_main!("zng-view-prebuilt/view-process" => view_process_main);
+
+fn view_process_main() -> ! {
+    ViewLib::install().unwrap().init();
+    std::process::exit(0)
 }
 
 /// Call the pre-build [`run_same_process`].
diff --git a/crates/zng-view/Cargo.toml b/crates/zng-view/Cargo.toml
index da6e8c8ca..8ace90313 100644
--- a/crates/zng-view/Cargo.toml
+++ b/crates/zng-view/Cargo.toml
@@ -50,7 +50,9 @@ zng-view-api = { path = "../zng-view-api", version = "0.4.4", default-features =
 zng-unit = { path = "../zng-unit", version = "0.2.6" }
 zng-txt = { path = "../zng-txt", version = "0.2.6" }
 zng-tp-licenses = { path = "../zng-tp-licenses", version = "0.2.6" }
+zng-env = { path = "../zng-env", version = "0.1.1" }
 
+linkme = "0.3"
 tracing = "0.1"
 gleam = "0.15.0" # matches webrender
 winit = { version = "0.30", default-features = false, features = [
diff --git a/crates/zng-view/src/extensions.rs b/crates/zng-view/src/extensions.rs
index 5cbc27aed..03e9f5f75 100644
--- a/crates/zng-view/src/extensions.rs
+++ b/crates/zng-view/src/extensions.rs
@@ -828,6 +828,11 @@ impl ViewExtensions {
             ext.low_memory();
         }
     }
+
+    /// Add `other` to self.
+    pub fn append(&mut self, mut other: ViewExtensions) {
+        self.exts.append(&mut other.exts);
+    }
 }
 
 /// Sets renderer debug flags.
diff --git a/crates/zng-view/src/image_cache.rs b/crates/zng-view/src/image_cache.rs
index af6d56775..bed760f64 100644
--- a/crates/zng-view/src/image_cache.rs
+++ b/crates/zng-view/src/image_cache.rs
@@ -922,7 +922,7 @@ impl Image {
         } else {
             let r = if width > 255 || height > 255 {
                 // resize to max 255
-                let mut buf = pixels.as_ref().to_vec();
+                let mut buf = pixels[..].to_vec();
                 // BGRA to RGBA
                 buf.chunks_exact_mut(4).for_each(|c| c.swap(0, 2));
                 let img = image::ImageBuffer::from_raw(width, height, buf).unwrap();
@@ -934,7 +934,7 @@ impl Image {
                 let buf = img.into_rgba8().into_raw();
                 winit::window::Icon::from_rgba(buf, width, height)
             } else {
-                let mut buf = pixels.as_ref().to_vec();
+                let mut buf = pixels[..].to_vec();
                 // BGRA to RGBA
                 buf.chunks_exact_mut(4).for_each(|c| c.swap(0, 2));
                 winit::window::Icon::from_rgba(buf, width, height)
@@ -964,7 +964,7 @@ impl Image {
         if width == 0 || height == 0 || hotspot_x > width || hotspot_y > height || self.0.is_mask() {
             None
         } else {
-            let mut buf = pixels.as_ref().to_vec();
+            let mut buf = pixels[..].to_vec();
             // BGRA to RGBA
             buf.chunks_exact_mut(4).for_each(|c| c.swap(0, 2));
             match CustomCursor::from_rgba(buf, width, height, hotspot_x, hotspot_y) {
diff --git a/crates/zng-view/src/lib.rs b/crates/zng-view/src/lib.rs
index 5c2821468..7d0134a42 100644
--- a/crates/zng-view/src/lib.rs
+++ b/crates/zng-view/src/lib.rs
@@ -15,7 +15,7 @@
 //! zng-view = "0.3.4"
 //! ```
 //!
-//! Then call [`init`] before any other code in `main` to setup a view-process that uses
+//! Then call `zng::env::init` before any other code in `main` to setup a view-process that uses
 //! the same app executable:
 //!
 //! ```
@@ -23,7 +23,7 @@
 //! use zng::prelude::*;
 //!
 //! fn main() {
-//!     zng_view::init();
+//!     zng::env::init!();
 //!
 //!     APP.defaults().run_window(|ctx| {
 //!         unimplemented!()
@@ -32,9 +32,8 @@
 //! # }}
 //! ```
 //!
-//! When the app is executed `init` setup its startup and returns, `run_window` gets called and
-//! internally starts the view-process, using the `init` setup. The current executable is started
-//! again, this time configured to be a view-process, `init` detects this and highjacks the process
+//! When the app is executed `run_window` gets called and internally starts the view-process.
+//! The current executable is started this time configured to be a view-process, `init` detects this and highjacks the process
 //! **never returning**.
 //!
 //! # Software Backend
@@ -67,19 +66,6 @@
 //! zng-view-prebuilt = "0.1"
 //! ```
 //!
-//! Then in the `main.rs` file:
-//!
-//! ```no_run
-//! # mod zng_view_prebuilt { pub fn init() { } }
-//! use zng_view_prebuilt as zng_view;
-//!
-//! fn main() {
-//!     zng_view::init();
-//!     
-//!     // APP.defaults().run ..
-//! }
-//! ```
-//!
 //! The pre-built crate includes the `"software"` and `"ipc"` features, in fact `ipc` is required, even for running on the same process,
 //! you can also configure where the pre-build library is installed, see the [`zng-view-prebuilt`] documentation for details.
 //!
@@ -165,46 +151,53 @@ use zng_view_api::{
 
 use rustc_hash::FxHashMap;
 
-/// Runs the view-process server if called in the environment of a view-process.
-///
-/// If this function is called in a process not configured to be a view-process it will return
-/// immediately, with the expectation that the app will be started. If called in a view-process
-/// if will highjack the process **never returning**.
-///
-/// # Panics
-///
-/// Panics if not called in the main thread, this is a requirement of some operating systems.
-///
-/// Panics if there was an error connecting with the app-process.
-///
-/// # Aborts
-///
-/// If called in a view-process a custom panic hook is set that logs panics to `stderr` and exits
-/// the process with exit code `101`. This is handled by the app-process by logging the error and
-/// attempting to respawn the view-process.
+/// Name of the view-process in [`zng_env::process_main!`].
 #[cfg(feature = "ipc")]
-pub fn init() {
-    init_extended(extensions::ViewExtensions::new)
+pub const ZNG_ENV_VIEW_PROCESS: &str = "zng-view/view-process";
+
+#[cfg(feature = "ipc")]
+zng_env::process_main!(ZNG_ENV_VIEW_PROCESS => view_process_main);
+
+/// Register a `fn() -> ViewExtensions` to be called on view-process init to inject custom API extensions.
+///
+/// See [`ViewExtensions`] for more details.
+#[macro_export]
+macro_rules! view_process_extension {
+    ($extension_fn:path) => {
+        #[doc(hidden)]
+        #[::linkme::distributed_slice($crate::VIEW_EXTENSIONS)]
+        static VIEW_EXTENSIONS: fn() -> ViewExtensions = $extension_fn;
+    };
 }
 
-/// Like [`init`] but with custom API extensions.
+#[doc(hidden)]
+#[linkme::distributed_slice]
+pub static VIEW_EXTENSIONS: [fn() -> ViewExtensions];
+
+/// Runs the view-process server.
+///
+/// Note that this only needs to be called if the view-process is not built on the same executable, if
+/// it is you only need to call [`zng_env::init!`] at the beginning of the executable main.
 #[cfg(feature = "ipc")]
-pub fn init_extended(ext: fn() -> ViewExtensions) {
-    if let Some(config) = ViewConfig::from_env() {
-        std::panic::set_hook(Box::new(init_abort));
+pub fn view_process_main() -> ! {
+    std::panic::set_hook(Box::new(init_abort));
 
-        config.assert_version(false);
+    let config = ViewConfig::from_env().expect("view-process missing env config");
+    config.assert_version(false);
+    let c = ipc::connect_view_process(config.server_name).expect("failed to connect to app-process");
 
-        let c = ipc::connect_view_process(config.server_name).expect("failed to connect to app-process");
+    let mut ext = ViewExtensions::new();
+    for e in VIEW_EXTENSIONS {
+        ext.append(e());
+    }
 
-        if config.headless {
-            App::run_headless(c, ext());
-        } else {
-            App::run_headed(c, ext());
-        }
+    if config.headless {
+        App::run_headless(c, ext);
     } else {
-        tracing::trace!("init not in view-process");
+        App::run_headed(c, ext);
     }
+
+    std::process::exit(0)
 }
 
 #[cfg(feature = "ipc")]
@@ -212,7 +205,7 @@ pub fn init_extended(ext: fn() -> ViewExtensions) {
 #[no_mangle]
 pub extern "C" fn extern_init() {
     std::panic::set_hook(Box::new(ffi_abort));
-    init()
+    view_process_main()
 }
 
 /// Runs the view-process server in the current process and calls `run_app` to also
diff --git a/crates/zng/README.md b/crates/zng/README.md
index 3729ed6fc..125fd9ad6 100644
--- a/crates/zng/README.md
+++ b/crates/zng/README.md
@@ -19,7 +19,7 @@ Then create your first window:
 use zng::prelude::*;
 
 fn main() {
-    zng::view_process::prebuilt::init();
+    zng::env::init!();
     APP.defaults().run_window(async {
         let size = var(layout::Size::new(800, 600));
         Window! {
diff --git a/crates/zng/src/app.rs b/crates/zng/src/app.rs
index 3385aa115..2aca7bc1d 100644
--- a/crates/zng/src/app.rs
+++ b/crates/zng/src/app.rs
@@ -14,11 +14,11 @@
 //!
 //! ## View-Process
 //!
-//! To simplify distribution the view-process is an instance of the same app executable, the view-process crate provides
-//! and `init` function that does nothing the process is not the view-process or takes over execution if it is, never returning.
+//! To simplify distribution the view-process is an instance of the same app executable, the view-process crate injects
+//! their own "main" in the [`zng::env::init!`] call, automatically taking over the process if the executable spawns as a view-process.
 //!
-//! On the first instance of the app executable the `init` function does nothing, the app init spawns a second process marked as
-//! the view-process, on this second instance the init function never returns, for this reason the function
+//! On the first instance of the app executable the `init` only inits the env and returns, the app init spawns a second process
+//! marked as the view-process, on this second instance the init call never returns, for this reason the init
 //! must be called early in main, all code before the `init` call runs in both the app and view processes.
 //!
 //! ```toml
@@ -31,7 +31,7 @@
 //!
 //! fn main() {
 //!     app_and_view();
-//!     zng::view_process::prebuilt::init(); // init only returns if it is not called in the view-process.
+//!     zng::env::init!(); // init only returns if it is not called in the view-process.
 //!     app();
 //! }
 //!
@@ -58,6 +58,7 @@
 //! use zng::prelude::*;
 //!
 //! fn main() {
+//!     zng::env::init!();
 //!     zng::view_process::prebuilt::run_same_process(app);
 //! }
 //!
@@ -69,6 +70,8 @@
 //! }
 //! ```
 //!
+//! Note that you must still call `init!` as it also initializes the app metadata and directories.
+//!
 //! # Headless
 //!
 //! The app can also run *headless*, where no window is actually created, optionally with real rendering.
@@ -445,12 +448,7 @@ pub use zng_ext_single_instance::{is_single_instance, single_instance, single_in
 /// fn main() {
 ///     // tracing applied to all processes.
 ///     zng::app::print_tracing(tracing::Level::INFO);
-///
-///     // worker-process
-///     // zng::task::ipc::run_worker(worker);
-///
-///     // view-process init for app-process and dialog-process.
-///     zng::view_process::prebuilt::init();
+///     zng::env::init!();
 ///     
 ///     // monitor-process or dialog-process init.
 ///     //
@@ -558,13 +556,6 @@ pub mod crash_handler {
             args.exit(0xBADC0DE)
         }
 
-        // Just in case no view-process was inited before the crash_handler setup.
-        // Multiple view-process inits are ok because they either do nothing or never return.
-        #[cfg(feature = "view_prebuilt")]
-        crate::view_process::prebuilt::init();
-        #[cfg(feature = "view")]
-        crate::view_process::default::init();
-
         APP.defaults()
             .run_window(async_clmv!(args, { zng_wgt_inspector::crash_handler::debug_dialog(args) }));
 
diff --git a/crates/zng/src/env.rs b/crates/zng/src/env.rs
index 1d1d3c4ad..fe297bf1e 100644
--- a/crates/zng/src/env.rs
+++ b/crates/zng/src/env.rs
@@ -22,6 +22,10 @@
 //!
 //! The example above uses [`init!`] to initialize the metadata used to find a good place for each directory, it then
 //! uses [`config`] to write and read a file.
+//!
+//! # Full API
+//!
+//! See [`zng_env`] for the full API.
 
 pub use zng_env::{
     about, bin, cache, clear_cache, config, init, init_cache, init_config, init_res, migrate_cache, migrate_config, res, About,
diff --git a/crates/zng/src/lib.rs b/crates/zng/src/lib.rs
index 134a938e3..d32763fe1 100644
--- a/crates/zng/src/lib.rs
+++ b/crates/zng/src/lib.rs
@@ -26,7 +26,7 @@
 //! use zng::prelude::*;
 //!
 //! fn main() {
-//!     zng::view_process::prebuilt::init();
+//!     zng::env::init!();
 //!     app();
 //! }
 //!
@@ -850,6 +850,11 @@ mod defaults {
         /// * [`HotReloadManager`] if the `"hot_reload"` feature is enabled.
         /// * [`MaterialFonts`] if any `"material_icons*"` feature is enabled.
         ///
+        /// # View-Process
+        ///
+        /// If the `"view_prebuilt"` feature is enabled configures the prebuilt view-process to run if `run_same_process` is not used. Else,
+        /// if the `"view"` feature is enabled configures it to run. If both are enabled logs a warning and uses prebuilt.
+        ///
         /// [`MaterialFonts`]: zng_wgt_material_icons::MaterialFonts
         /// [`SingleInstanceManager`]: zng_ext_single_instance::SingleInstanceManager
         /// [`HotReloadManager`]: zng_ext_hot_reload::HotReloadManager
@@ -874,6 +879,14 @@ mod defaults {
                 .extend(ClipboardManager::default())
                 .extend(UndoManager::default());
 
+            #[cfg(feature = "view")]
+            let r = r.view_process_env(zng_env::PROCESS_MAIN, zng_view::ZNG_ENV_VIEW_PROCESS);
+            #[cfg(feature = "view_prebuilt")]
+            let r = r.view_process_env(zng_env::PROCESS_MAIN, zng_view_prebuilt::ZNG_ENV_VIEW_PROCESS);
+
+            #[cfg(all(feature = "view", feature = "view_prebuilt"))]
+            tracing::warn!(r#"both "view" and "view_prebuilt" enabled, will use "view_prebuilt""#);
+
             #[cfg(feature = "single_instance")]
             let r = r.extend(zng_ext_single_instance::SingleInstanceManager::default());
 
diff --git a/crates/zng/src/view_process.rs b/crates/zng/src/view_process.rs
index ded2eb3ff..213290f73 100644
--- a/crates/zng/src/view_process.rs
+++ b/crates/zng/src/view_process.rs
@@ -13,7 +13,7 @@
 //! }
 //!
 //! fn multi_process() {
-//!     view_process::init();
+//!     zng::env::init!();
 //!     app();
 //! }
 //!
@@ -38,7 +38,7 @@
 /// See [`zng_view`] for the full API.
 #[cfg(feature = "view")]
 pub mod default {
-    pub use zng_view::{init, run_same_process};
+    pub use zng_view::run_same_process;
 }
 
 /// Default view-process implementation as an embedded precompiled binary.
@@ -48,5 +48,5 @@ pub mod default {
 /// See [`zng_view_prebuilt`] and [`zng_view`] for the full API.
 #[cfg(feature = "view_prebuilt")]
 pub mod prebuilt {
-    pub use zng_view_prebuilt::{init, run_same_process};
+    pub use zng_view_prebuilt::run_same_process;
 }
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index d01b4bcdb..adf091245 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -13,6 +13,7 @@ regex = "1.10"
 serde = { version = "1.0", features = ["derive"] }
 raw-window-handle = "0.6"
 dunce = "1.0"
+linkme = "0.3"
 
 zng = { path = "../crates/zng", features = [
     "http",
@@ -26,9 +27,9 @@ zng = { path = "../crates/zng", features = [
     # "multi_app",
 ] }
 
-zng-app = { path = "../crates/zng-app" }           # for respawn and extend_view examples
-zng-view-api = { path = "../crates/zng-view-api" } # for the extend_view example
-zng-view = { path = "../crates/zng-view" } # for the extend_view example
+zng-app = { path = "../crates/zng-app" }                                 # for respawn and extend_view examples
+zng-view-api = { path = "../crates/zng-view-api" }                       # for the extend_view example
+zng-view = { path = "../crates/zng-view" }                               # for the extend_view example
 zng-wgt-webrender-debug = { path = "../crates/zng-wgt-webrender-debug" } # for the extend_view example
 
 examples-util = { path = "util" }
diff --git a/examples/README.md b/examples/README.md
index 4424ad896..a8facce9c 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -290,7 +290,7 @@ use zng::view_process::prebuilt as view_process;
 fn main() {
     examples_util::print_info();
 
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
     app_main();
 }
diff --git a/examples/animation.rs b/examples/animation.rs
index b5f0a6a20..5e489a9a9 100644
--- a/examples/animation.rs
+++ b/examples/animation.rs
@@ -22,16 +22,14 @@ use zng::{
     window::RenderMode,
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("animation");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/border.rs b/examples/border.rs
index b5f0a0303..42a910548 100644
--- a/examples/border.rs
+++ b/examples/border.rs
@@ -6,16 +6,14 @@ use zng::{
     widget::{background_color, border_align, corner_radius},
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     //let rec = examples_util::record_profile("border");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/button.rs b/examples/button.rs
index cc34b37fb..f1f5726b0 100644
--- a/examples/button.rs
+++ b/examples/button.rs
@@ -12,16 +12,14 @@ use zng::{
     widget::{node::EditableUiNodeList, LineStyle},
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("button");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/calculator.rs b/examples/calculator.rs
index fb43e2f4e..148902aeb 100644
--- a/examples/calculator.rs
+++ b/examples/calculator.rs
@@ -7,16 +7,14 @@ use zng::{
     prelude::*,
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     //let rec = examples_util::record_profile("calculator");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     //rec.finish();
diff --git a/examples/config.rs b/examples/config.rs
index cd95ecbe3..144295178 100644
--- a/examples/config.rs
+++ b/examples/config.rs
@@ -12,16 +12,14 @@ use zng::{
 
 use zng::config::*;
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("button");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/countdown.rs b/examples/countdown.rs
index 81a75535b..6609b3ec4 100644
--- a/examples/countdown.rs
+++ b/examples/countdown.rs
@@ -3,15 +3,11 @@
 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
 use zng::{image, prelude::*, widget::background_color};
 
-use zng::view_process::default as view_process;
-
 fn main() {
     examples_util::print_info();
-    // view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
-
-    view_process::run_same_process(app_main);
-    // app_main();
+    app_main();
 }
 
 fn app_main() {
diff --git a/examples/cursor.rs b/examples/cursor.rs
index 4d62c953f..6b5331e9d 100644
--- a/examples/cursor.rs
+++ b/examples/cursor.rs
@@ -10,15 +10,11 @@ use zng::{
     prelude_wgt::NilUiNode,
 };
 
-use zng::view_process::default as view_process;
-
 fn main() {
     examples_util::print_info();
-    // view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
-
-    view_process::run_same_process(app_main);
-    // app_main();
+    app_main();
 }
 
 fn app_main() {
diff --git a/examples/extend_view.rs b/examples/extend_view.rs
index 058bffb30..7bf4e005c 100644
--- a/examples/extend_view.rs
+++ b/examples/extend_view.rs
@@ -11,14 +11,15 @@ use zng_view::extensions::ViewExtensions;
 
 fn main() {
     examples_util::print_info();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
-    zng_view::init_extended(view_extensions);
-    app_main();
-
     // zng_view::run_same_process_extended(app_main, view_extensions);
+    app_main();
 }
 
+zng_view::view_process_extension!(view_extensions);
+
 fn app_main() {
     APP.defaults().run_window(async {
         Window! {
diff --git a/examples/focus.rs b/examples/focus.rs
index c02e6b844..f27bb4fe7 100644
--- a/examples/focus.rs
+++ b/examples/focus.rs
@@ -20,16 +20,14 @@ use zng::{
     window::FocusIndicator,
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("focus");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/gradient.rs b/examples/gradient.rs
index b5ddc21c6..6c39bf904 100644
--- a/examples/gradient.rs
+++ b/examples/gradient.rs
@@ -13,14 +13,12 @@ use zng::{
     text::ToTxt,
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 }
 
diff --git a/examples/headless.rs b/examples/headless.rs
index b8c80937e..307106d47 100644
--- a/examples/headless.rs
+++ b/examples/headless.rs
@@ -13,14 +13,11 @@ use zng::{
     window::{FrameCaptureMode, FrameImageReadyArgs, HeadlessAppWindowExt},
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
-
-    // view_process::run_same_process(headless_example);
+    zng::env::init!();
 
+    // zng::view_process::prebuilt::run_same_process(headless_example);
     headless_example();
     // headless_example_video();
     // images_render();
diff --git a/examples/hot_reload.rs b/examples/hot_reload.rs
index b7ae92de3..88ad0cdf9 100644
--- a/examples/hot_reload.rs
+++ b/examples/hot_reload.rs
@@ -4,11 +4,9 @@
 
 use zng::prelude::*;
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
     app_main();
 }
diff --git a/examples/icon.rs b/examples/icon.rs
index aeec417cf..3316c38cf 100644
--- a/examples/icon.rs
+++ b/examples/icon.rs
@@ -22,16 +22,14 @@ use zng::{
     wrap,
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("icon");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/image.rs b/examples/image.rs
index 7436b3fa3..cc423f461 100644
--- a/examples/image.rs
+++ b/examples/image.rs
@@ -4,7 +4,6 @@
 
 use std::path::PathBuf;
 
-use zng::view_process::prebuilt as view_process;
 use zng::{
     app, button,
     checkerboard::Checkerboard,
@@ -26,14 +25,12 @@ use zng_wgt_webrender_debug as wr;
 
 fn main() {
     examples_util::print_info();
-    // view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("image");
 
-    view_process::run_same_process(app_main);
-
-    // app_main();
+    app_main();
     // rec.finish();
 }
 
diff --git a/examples/layer.rs b/examples/layer.rs
index de9d62ee8..a53d5e553 100644
--- a/examples/layer.rs
+++ b/examples/layer.rs
@@ -12,16 +12,14 @@ use zng::{
     widget::{background_color, border, hit_test_mode, HitTestMode},
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("layer");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/localize.rs b/examples/localize.rs
index 4e5eaf428..94cf54533 100644
--- a/examples/localize.rs
+++ b/examples/localize.rs
@@ -23,16 +23,14 @@ use zng::{
 // Run this command to scrap template:
 // cargo run -p cargo-zng -- l10n "examples/localize*" "examples/res/localize"
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("localize");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/markdown.rs b/examples/markdown.rs
index 7961f43d0..e224bfd74 100644
--- a/examples/markdown.rs
+++ b/examples/markdown.rs
@@ -10,16 +10,14 @@ use zng::{
     scroll::ScrollMode,
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("markdown");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/respawn.rs b/examples/respawn.rs
index 814f35c8a..e3f5204c2 100644
--- a/examples/respawn.rs
+++ b/examples/respawn.rs
@@ -13,18 +13,20 @@ use zng_view::extensions::ViewExtensions;
 
 fn main() {
     examples_util::print_info();
+    // init metadata, view-process, crash-dialog-process.
+    zng::env::init!();
 
     // init crash-handler before view to use different view for the crash dialog app.
     zng::app::crash_handler::init_debug();
     // zng::app::crash_handler::init(zng::app::crash_handler::CrashConfig::new(app_crash_dialog));
 
     // this is the normal app-process:
-
-    // init view with extensions used to cause a crash in the view-process.
-    zng_view::init_extended(test_extensions);
     app_main();
 }
 
+// init view with extensions used to cause a crash in the view-process.
+zng_view::view_process_extension!(test_extensions);
+
 fn app_main() {
     APP.defaults().run_window(async {
         Window! {
@@ -88,7 +90,6 @@ fn app_main() {
 // Crash dialog app, runs in the dialog-process.
 #[allow(unused)]
 fn app_crash_dialog(args: zng::app::crash_handler::CrashArgs) -> ! {
-    zng::view_process::prebuilt::init();
     APP.defaults().run_window(async move {
         Window! {
             title = "Respawn Example - App Crashed";
diff --git a/examples/scroll.rs b/examples/scroll.rs
index 9b55a316f..1f26d2177 100644
--- a/examples/scroll.rs
+++ b/examples/scroll.rs
@@ -8,17 +8,15 @@ use zng::{
     scroll::cmd::ScrollToMode,
 };
 
-use zng::view_process::prebuilt as view_process;
-
 use rand::SeedableRng;
 
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("scroll");
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/shortcut.rs b/examples/shortcut.rs
index c55c82bce..48d0018c2 100644
--- a/examples/shortcut.rs
+++ b/examples/shortcut.rs
@@ -4,16 +4,14 @@
 
 use zng::{font::FontName, layout::align, prelude::*};
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("shortcuts");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/text.rs b/examples/text.rs
index a4a861bab..19d9f2389 100644
--- a/examples/text.rs
+++ b/examples/text.rs
@@ -27,16 +27,14 @@ use zng::{
     window::{native_dialog, WindowRoot},
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     //let rec = examples_util::record_profile("text");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/transform.rs b/examples/transform.rs
index bcf5288bb..4faa7341a 100644
--- a/examples/transform.rs
+++ b/examples/transform.rs
@@ -16,16 +16,14 @@ use zng::{
     window::WindowState,
 };
 
-use zng::view_process::prebuilt as view_process;
-
 fn main() {
     examples_util::print_info();
-    view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("transform");
 
-    // view_process::run_same_process(app_main);
+    // zng::view_process::prebuilt::run_same_process(app_main);
     app_main();
 
     // rec.finish();
diff --git a/examples/window.rs b/examples/window.rs
index a39b91dd9..6fedb6c9d 100644
--- a/examples/window.rs
+++ b/examples/window.rs
@@ -24,16 +24,14 @@ use zng::{
     window::{native_dialog, FocusIndicator, FrameCaptureMode, FrameImageReadyArgs, WindowChangedArgs, WindowState},
 };
 
-use zng::view_process::default as view_process;
-
 fn main() {
     examples_util::print_info();
-    // view_process::init();
+    zng::env::init!();
     zng::app::crash_handler::init_debug();
 
     // let rec = examples_util::record_profile("window");
 
-    view_process::run_same_process(app_main);
+    zng::view_process::default::run_same_process(app_main);
     // app_main();
 
     // rec.finish();


Test patch:
diff --git a/tests/render-tests/src/main.rs b/tests/render-tests/src/main.rs
index fcfc3445a..3ccb8b2a6 100644
--- a/tests/render-tests/src/main.rs
+++ b/tests/render-tests/src/main.rs
@@ -50,14 +50,14 @@ fn main() {
 fn run(args: Args, view_process: ViewProcess) {
     match view_process {
         ViewProcess::DefaultInit => {
-            view_process::default::init();
+            zng::env::init!();
             run_tests(args, view_process, APP.defaults().run_headless(true));
         }
         ViewProcess::DefaultSame => {
             view_process::default::run_same_process(move || run_tests(args, view_process, APP.defaults().run_headless(true)))
         }
         ViewProcess::PrebuiltInit => {
-            view_process::prebuilt::init();
+            zng::env::init!();
             run_tests(args, view_process, APP.defaults().run_headless(true));
         }
         ViewProcess::PrebuiltSame => {