use std::collections::{HashMap, HashSet};
use std::sync::{Arc, LazyLock, Mutex};
use lingxia_platform::traits::app_runtime::AppRuntime;
use lingxia_platform::traits::ui::SurfaceContent;
pub use lingxia_platform::{Platform, PlatformError, set_windows_app_exit_handler};
use lingxia_webview::{WebTag, WebViewController};
static WINDOWS_APP_VISIBLE_WEBTAGS: LazyLock<Mutex<HashMap<String, HashSet<String>>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub fn init(platform: Platform) -> Option<String> {
crate::logging::init();
lingxia_webview::platform::windows::set_webview_user_data_dir(
platform.app_cache_dir().join("webview2"),
);
install_lifecycle_bridge();
install_url_surface_bridge();
crate::init_with_platform(platform)
}
pub fn open_home_app(appid: &str) -> Result<(), String> {
lxapp::open_lxapp(appid, lxapp::LxAppStartupOptions::new(""))
.map(|_| ())
.map_err(|err| err.to_string())
}
pub fn set_default_window_size(width: i32, height: i32) {
lingxia_windows_contract::set_default_window_size(width, height);
}
pub fn resize_app_window_content(appid: &str, width: i32, height: i32) -> Result<(), String> {
let webview = current_page_webview(appid)?;
lingxia_windows_contract::resize_host_window_content(&webview.webtag(), width, height)
.map_err(|err| err.to_string())
}
pub fn set_surface_width(appid: &str, width: f64) -> bool {
lxapp::try_get(appid)
.map(|app| app.set_surface_width(width))
.unwrap_or(false)
}
fn current_page_webview(appid: &str) -> Result<std::sync::Arc<lingxia_webview::WebView>, String> {
let app = lxapp::try_get(appid).ok_or_else(|| format!("lxapp is not active: {appid}"))?;
let page = app.current_page().map_err(|err| err.to_string())?;
page.webview()
.ok_or_else(|| "page WebView is not ready".to_string())
}
fn install_lifecycle_bridge() {
lingxia_windows_contract::set_webview_visibility_handler(Arc::new(|webtag, visible| {
on_webview_visibility_changed(webtag, visible);
}));
}
fn install_url_surface_bridge() {
lingxia_platform::set_windows_url_surface_handler(Arc::new(|request| {
if request.content != SurfaceContent::Url {
return None;
}
let webtag = WebTag::new(&request.app_id, &request.path, Some(request.session_id));
let url = request.path.clone();
let session = lingxia_webview::WebViewBuilder::browser(webtag).create();
std::mem::drop(crate::task::spawn(async move {
match session.wait_ready().await {
Ok(webview) => {
if let Err(err) = webview.load_url(&url) {
log::error!("URL surface failed to load {url}: {err}");
}
}
Err(err) => log::error!("URL surface webview create failed for {url}: {err}"),
}
}));
Some(lingxia_platform::WindowsUrlSurfaceWebTag {
app_id: request.app_id.clone(),
path: request.path.clone(),
session_id: request.session_id,
cleanup: None,
})
}));
}
fn on_webview_visibility_changed(webtag: &WebTag, visible: bool) {
let (appid, path) = webtag.extract_parts();
if appid.is_empty() || path.is_empty() {
return;
}
if let Err(err) = lxapp::notify_page_host_visibility(&appid, &path, visible) {
log::debug!(
"Windows page visibility event ignored for {} visible={}: {}",
webtag,
visible,
err
);
}
let app_event = update_app_visible_webtags(&appid, webtag.key(), visible);
if let Some(visible) = app_event
&& let Err(err) = lxapp::notify_lxapp_host_visibility(&appid, visible)
{
log::debug!(
"Windows app visibility event ignored for {} visible={}: {}",
appid,
visible,
err
);
}
}
fn update_app_visible_webtags(appid: &str, webtag_key: &str, visible: bool) -> Option<bool> {
let Ok(mut visible_webtags) = WINDOWS_APP_VISIBLE_WEBTAGS.lock() else {
return None;
};
let webtags = visible_webtags.entry(appid.to_string()).or_default();
let was_visible = !webtags.is_empty();
if visible {
webtags.insert(webtag_key.to_string());
} else {
webtags.remove(webtag_key);
}
let is_visible = !webtags.is_empty();
if !is_visible {
visible_webtags.remove(appid);
}
(was_visible != is_visible).then_some(is_visible)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn app_visibility_is_aggregated_across_webtags() {
WINDOWS_APP_VISIBLE_WEBTAGS.lock().unwrap().clear();
assert_eq!(
update_app_visible_webtags("app", "app:main", true),
Some(true)
);
assert_eq!(update_app_visible_webtags("app", "app:panel", true), None);
assert_eq!(update_app_visible_webtags("app", "app:panel", false), None);
assert_eq!(
update_app_visible_webtags("app", "app:main", false),
Some(false)
);
}
}