mod app_sdk;
mod bridge;
mod command;
mod create;
mod event;
#[cfg(feature = "application-ipc")]
mod ipc;
#[cfg(feature = "application-ipc")]
mod ipc_body;
#[cfg(feature = "application-ipc")]
mod ipc_control;
#[cfg(feature = "application-ipc")]
mod ipc_deadline;
#[cfg(feature = "application-ipc")]
mod ipc_http;
#[cfg(feature = "application-ipc")]
mod ipc_policy;
mod message;
mod nonclient;
mod protocol;
mod request;
mod state;
mod wakeup;
mod webview;
use std::cell::Cell;
#[cfg(feature = "application-ipc")]
use std::rc::Rc;
use std::sync::Arc;
use crate::{DesktopEvent, DesktopRuntime, WindowId, WindowOptions, WindowStateStore};
use anyhow::{Context, Result};
use windows::core::w;
use windows::core::PCWSTR;
use windows::Win32::Foundation::{E_ACCESSDENIED, LPARAM, WPARAM};
use windows::Win32::Graphics::Gdi;
use windows::Win32::System::Com::{CoInitializeEx, CoUninitialize, COINIT_APARTMENTTHREADED};
use windows::Win32::UI::HiDpi;
use windows::Win32::UI::Input::KeyboardAndMouse;
use windows::Win32::UI::WindowsAndMessaging;
use crate::DesktopFrame;
use create::FrameWindow;
use state::FrameState;
pub(super) const APP_ORIGIN: &str = "https://app.webui.localhost";
pub(super) const APP_REQUEST_FILTER: PCWSTR = w!("https://app.webui.localhost/*");
pub(super) const WAKE_MESSAGE: u32 = WindowsAndMessaging::WM_APP + 1;
pub(super) const APP_WAKE_MESSAGE: u32 = WindowsAndMessaging::WM_APP + 3;
mod tasks;
#[cfg(feature = "application-ipc")]
pub(super) const IPC_WAKE_MESSAGE: u32 = WindowsAndMessaging::WM_APP + 2;
pub(super) const WINDOW_ID: WindowId = WindowId::PRIMARY;
pub fn run_packaged_app() -> Result<()> {
crate::run_packaged_app().map_err(Into::into)
}
pub fn run_runtime(runtime: Arc<DesktopRuntime>, window: WindowOptions) -> Result<()> {
crate::run_runtime(runtime, window).map_err(Into::into)
}
pub(crate) fn run_frame(frame: DesktopFrame) -> Result<()> {
let store = WindowStateStore::for_window(frame.window.remember_state, frame.app_id.as_deref())?;
let _com = initialize_com()?;
configure_dpi_awareness()?;
let runtime = app_sdk::Runtime::initialize()?;
let saved = state::load_saved_state(store.as_ref());
let window_frame = FrameWindow::new(&frame.window, saved.as_ref())?;
let app_window = app_sdk::WindowFrame::attach(&runtime, window_frame.hwnd, &frame.window)?;
let profile = webview::browser_profile(frame.app_id.as_deref())?;
let environment = webview::create_environment(&profile.path).with_context(|| {
"Failed to initialize WebView2; install the Microsoft Edge WebView2 Runtime or use a Windows image that includes it"
})?;
let content = window_frame.hwnd;
let controller = webview::create_controller(&environment, content)?;
webview::configure_controller_background(&controller, frame.window.background)?;
webview::configure_window_effect(window_frame.hwnd, frame.window.effect);
let webview = unsafe { controller.CoreWebView2()? };
webview::configure_settings(&webview, frame.window.devtools)?;
#[cfg(feature = "application-ipc")]
let ipc = ipc::WindowsIpc::new(frame.ipc_bridge(), &webview, window_frame.hwnd)?;
#[cfg(feature = "application-ipc")]
let _ipc_shutdown = ipc::Shutdown(Rc::clone(&ipc));
let navigation_starting = webview::register_navigation_guard(&webview, frame.events.clone())?;
let navigation_completed = webview::register_navigation_completed(
&webview,
frame.events.clone(),
window_frame.hwnd,
frame.runtime.live_background(),
)?;
webview::inject_drag_script(&webview)?;
app_window.install_metrics(&webview)?;
let web_message_received = bridge::register_message_handler(
&webview,
window_frame.hwnd,
#[cfg(feature = "application-ipc")]
Rc::downgrade(&ipc),
)?;
let application_tasks = tasks::ApplicationTasks::new(window_frame.hwnd);
let web_resource_requested = protocol::register_runtime_handler(
&environment,
&webview,
&frame,
std::rc::Rc::downgrade(&application_tasks),
#[cfg(feature = "application-ipc")]
Rc::downgrade(&ipc),
)?;
message::set_controller_bounds(&controller, content)?;
unsafe { controller.SetIsVisible(true)? };
let state = Box::new(FrameState {
app_window,
content,
application_tasks,
#[cfg(feature = "application-ipc")]
ipc: Rc::clone(&ipc),
controller,
_navigation_starting: navigation_starting,
_navigation_completed: navigation_completed,
_web_message_received: web_message_received,
_web_resource_requested: web_resource_requested,
events: frame.events.clone(),
window_handle: frame.window_handle.clone(),
options: frame.window.clone(),
webview: webview.clone(),
store,
fullscreen: Cell::new(false),
window_state: Cell::new(state::initial_window_size_state(window_frame.hwnd)),
});
state::set_window_state(window_frame.hwnd, Some(state));
install_wakeup(window_frame.hwnd)?;
#[cfg(feature = "application-ipc")]
ipc.install(crate::ipc_assets::NATIVE_BOOTSTRAP_SCRIPT)?;
if frame.window.fullscreen {
state::with_window_state(window_frame.hwnd, |state| {
command::set_fullscreen(window_frame.hwnd, state, true);
});
}
window_frame.show()?;
message::refresh_frame(window_frame.hwnd);
unsafe {
let _ = Gdi::UpdateWindow(window_frame.hwnd);
let _ = KeyboardAndMouse::SetFocus(Some(window_frame.hwnd));
}
message::publish(window_frame.hwnd, &DesktopEvent::Ready);
webview::navigate_to_startup_url(&webview)?;
let message_loop_result = message::message_loop();
let _ = frame.events.dispatch(&DesktopEvent::Exiting);
message_loop_result
}
fn install_wakeup(hwnd: windows::Win32::Foundation::HWND) -> Result<()> {
let receiver = state::with_window_state_result(hwnd, |state| state.window_handle.clone());
let handle = hwnd.0 as usize;
wakeup::attach(receiver, move || {
let hwnd = windows::Win32::Foundation::HWND(handle as *mut std::ffi::c_void);
let _ = unsafe {
WindowsAndMessaging::PostMessageW(Some(hwnd), WAKE_MESSAGE, WPARAM(0), LPARAM(0))
};
})
}
struct ComApartment;
impl Drop for ComApartment {
fn drop(&mut self) {
unsafe { CoUninitialize() };
}
}
fn initialize_com() -> Result<ComApartment> {
unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok()? };
Ok(ComApartment)
}
fn configure_dpi_awareness() -> Result<()> {
match unsafe { HiDpi::SetProcessDpiAwareness(HiDpi::PROCESS_PER_MONITOR_DPI_AWARE) } {
Ok(()) => Ok(()),
Err(error) if error.code() == E_ACCESSDENIED => Ok(()),
Err(error) => Err(error.into()),
}
}