show_image/backend/
mod.rs

1mod context;
2mod event;
3mod mouse_cache;
4mod proxy;
5mod util;
6mod window;
7
8pub use context::ContextHandle;
9pub use proxy::ContextProxy;
10pub use proxy::WindowProxy;
11pub use window::WindowHandle;
12pub use window::WindowOptions;
13
14use crate::error;
15use context::Context;
16use std::panic::{AssertUnwindSafe, catch_unwind};
17
18static CONTEXT_PROXY: std::sync::OnceLock<ContextProxy> = std::sync::OnceLock::new();
19
20/// Initialize the global context.
21fn initialize_context() -> Result<Context, error::GetDeviceError> {
22	let context = Context::new(wgpu::TextureFormat::Bgra8Unorm)?;
23	if let Err(_) = CONTEXT_PROXY.set(context.proxy.clone()) {
24		panic!("show_image: context already initialized");
25	}
26	Ok(context)
27}
28
29/// Initialize the global context, or exit the process.
30fn initialize_context_or_exit() -> Context {
31	match initialize_context() {
32		Ok(x) => x,
33		Err(crate::error::GetDeviceError::NoSuitableDeviceFound(e)) => {
34			eprintln!("show-image: Failed to find a suitable device: {}. Terminating process.", e);
35			std::process::exit(-1);
36		},
37		Err(crate::error::GetDeviceError::NoSuitableAdapterFound(_)) => {
38			eprintln!("show-image: Failed to find a suitable graphics adapter. Terminating process.");
39			#[cfg(any(target_os = "android", target_os = "linux"))]
40			eprintln!("show-image: You may be missing the correct driver. Consider installing the Vulkan driver for your GPU.");
41			std::process::exit(-2);
42		}
43	}
44}
45
46/// Initialize and run the global context and spawn a user task in a new thread.
47///
48/// This function never returns.
49/// Once the user task finishes, the program exits with status code 0.
50/// Any background threads spawned by `show-image` will be joined before the process exits.
51/// It is the responsibility of the user code to join any manually spawned tasks.
52///
53/// The user task can call the [`context()`] function to obtain a [`ContextProxy`],
54/// or the [`create_window()`] function to create a new window directly.
55///
56/// If the `macros` feature is enabled, you can also wrap your main function with the [`main`][crate::main] macro
57/// instead of manually calling this function.
58///
59/// It is also possible to run a user task in the same thread as the context.
60/// See [`run_context_with_local_task()`] for more details.
61///
62/// # Panics
63/// This function panics if initialization of the global context fails.
64/// See [`try_run_context`] for a variant that allows the user task to handle these initialization errors.
65///
66/// This function also panics if it is called from any thread other than the main thread.
67/// Some platforms like OS X require all GUI code to run in the main thread.
68/// To ensure portability, this restriction is also enforced on other platforms.
69pub fn run_context<F, R>(user_task: F) -> !
70where
71	F: FnOnce() -> R + Send + 'static,
72	R: crate::termination::Termination,
73{
74	let context = initialize_context_or_exit();
75
76	// Spawn the user task.
77	std::thread::spawn(move || {
78		match catch_unwind(AssertUnwindSafe(user_task)) {
79			Ok(termination) => exit(termination.report()),
80			Err(_) => {
81				// Make sure the main thread panics too.
82				crate::context().run_function(move |_| {
83					panic!("show-image: main user task panicked");
84				});
85			},
86		}
87	});
88
89	context.run();
90}
91
92/// Initialize and run the global context and spawn a user task in a new thread.
93///
94/// This function is almost identical to [`run_context`],
95/// except that it allows the user task to handle initialization errors.
96/// If the initialization of the global context fails, the user task will be executed in the calling thread.
97/// If initialization succeeds, the user task is started in a newly spawned thread.
98///
99/// Whether or not initialization succeeded, the process will exit once the user task returns.
100/// Any background threads spawned by `show-image` will be joined before the process exits.
101/// It is the responsibility of the user code to join any manually spawned tasks.
102///
103/// It is also possible to run a user task in the same thread as the context.
104/// See [`try_run_context_with_local_task()`] for more details.
105///
106/// # Panics
107/// This function panics if it is called from any thread other than the main thread.
108/// Some platforms like OS X require all GUI code to run in the main thread.
109/// To ensure portability, this restriction is also enforced on other platforms.
110pub fn try_run_context<F, R>(user_task: F) -> !
111where
112	F: FnOnce(Result<(), error::GetDeviceError>) -> R + Send + 'static,
113	R: crate::termination::Termination,
114{
115	let context = match initialize_context() {
116		Ok(x) => x,
117		Err(e) => {
118			let termination = (user_task)(Err(e));
119			std::process::exit(termination.report());
120		},
121	};
122
123	// Spawn the user task.
124	std::thread::spawn(move || {
125		match catch_unwind(AssertUnwindSafe(move || user_task(Ok(())))) {
126			Ok(termination) => exit(termination.report()),
127			Err(_) => {
128				// Make sure the main thread panics too.
129				crate::context().run_function(move |_| {
130					panic!("show-image: main user task panicked");
131				});
132			},
133		}
134	});
135
136	context.run();
137}
138
139/// Initialize and run the global context and run a user task, both in the main thread.
140///
141/// The global context will execute the user function in the main thread after the context is fully initialized.
142///
143/// This function never returns.
144/// The global context will keep running after the local task finishes.
145/// It is up to the user code to call [`std::process::exit`] when the process should exit.
146/// Alternatively, you could call [`ContextHandle::set_exit_with_last_window`].
147///
148/// *Note*:
149/// You should not run a function that blocks for any significant time in the main thread.
150/// Doing so will prevent the event loop from processing events and will result in unresponsive windows.
151///
152/// If you're looking for a place to run your own application code,
153/// you probably want to use [`run_context`] or the [`main`][crate::main] macro.
154/// However, if you can drive your entire application from event handlers,
155/// then this function is probably what you're looking for.
156///
157/// # Panics
158/// This function panics if initialization of the global context fails.
159/// See [`try_run_context_with_local_task`] for a variant that allows the user task to handle initialization errors.
160///
161/// This function also panics if it is called from any thread other than the main thread.
162/// Some platforms like OS X require all GUI code to run in the main thread.
163/// To ensure portability, this restriction is also enforced on other platforms.
164pub fn run_context_with_local_task<F>(user_task: F) -> !
165where
166	F: FnOnce(&mut ContextHandle) + Send + 'static,
167{
168	let context = initialize_context_or_exit();
169
170	// Queue the user task.
171	// It won't be executed until context.run() is called.
172	context.proxy.run_function(user_task);
173	context.run();
174}
175
176/// Initialize and run the global context and run a user task, both in the main thread.
177///
178/// This function is almost identical to [`run_context_with_local_task`],
179/// except that it allows the user task to handle initialization errors.
180/// If the initialization of the global context fails, the process will terminate when the user task returns.
181/// Otherwise, the global context will continue running the event loop in the main thread.
182///
183/// # Panics
184/// This function panics if it is called from any thread other than the main thread.
185/// Some platforms like OS X require all GUI code to run in the main thread.
186/// To ensure portability, this restriction is also enforced on other platforms.
187pub fn try_run_context_with_local_task<F>(user_task: F) -> !
188where
189	F: FnOnce(Result<&mut ContextHandle, error::GetDeviceError>) + Send + 'static,
190{
191	let context = match initialize_context() {
192		Ok(x) => x,
193		Err(e) => {
194			(user_task)(Err(e));
195			std::process::exit(0);
196		},
197	};
198
199	// Queue the user task.
200	// It won't be executed until context.run() is called.
201	context.proxy.run_function(|context| user_task(Ok(context)));
202	context.run();
203}
204
205/// Get the global context to interact with existing windows or to create new windows.
206///
207/// If you manually spawn threads that try to access the context before calling `run_context`, you introduce a race condition.
208/// Instead, you should pass a function to [`run_context`] or one of the variants.
209/// Those functions take care to initialize the global context before running the user code.
210///
211/// # Panics
212/// This panics if the global context is not yet fully initialized.
213pub fn context() -> ContextProxy {
214	match CONTEXT_PROXY.get() {
215		Some(proxy) => proxy.clone(),
216		None => panic!("show_image: context not initialized"),
217	}
218}
219
220/// Create a new window with the global context.
221///
222/// If you manually spawn threads that try to access the context before calling `run_context`, you introduce a race condition.
223/// Instead, you should pass a function to [`run_context`] that will be started in a new thread after the context is initialized.
224///
225/// # Panics
226/// This panics if the global context is not yet fully initialized.
227pub fn create_window(title: impl Into<String>, options: WindowOptions) -> Result<WindowProxy, error::CreateWindowError> {
228	let title = title.into();
229	context().run_function_wait(move |context| {
230		let window = context.create_window(title, options)?;
231		Ok(window.proxy())
232	})
233}
234
235/// Join all background tasks and then exit the process.
236///
237/// If you use [`std::process::exit`], running background tasks may be killed.
238/// To ensure no data loss occurs, you should use this function instead.
239///
240/// Background tasks are spawned when an image is saved through the built-in Ctrl+S or Ctrl+Shift+S shortcut, or by user code.
241pub fn exit(code: i32) -> ! {
242	context().exit(code);
243}