use lingxia_messaging::invoke_callback;
use lxapp::{
CloseReason, CreatePageInstanceRequest, LxAppDelegate, LxAppUiEventType, OrientationConfig,
PageInstanceEvent, PageInstanceId, PageOrientation, PageOwner, PageQueryInput, PageTarget,
PresentationKind, SceneId,
};
#[cfg(all(target_os = "macos", feature = "browser-shell"))]
use std::sync::Arc;
fn parse_color_to_u32(color_str: &str, default_color: u32) -> u32 {
if color_str.eq_ignore_ascii_case("transparent") {
return 0x00000000;
}
if let Some(hex_part) = color_str.strip_prefix('#')
&& hex_part.len() == 6
&& let Ok(rgb) = u32::from_str_radix(hex_part, 16)
{
return 0xFF000000 | rgb; }
default_color
}
#[swift_bridge::bridge]
mod bridge {
#[swift_bridge(swift_repr = "struct")]
pub struct LxAppInfo {
pub app_name: String,
pub version: String,
pub release_type: String,
pub cache_dir: String,
pub icon: String,
}
#[swift_bridge(swift_repr = "struct")]
pub struct NavigationBarState {
pub background_color: u32,
pub text_style: String,
pub title_text: String,
pub show_navbar: bool,
pub show_back_button: bool,
pub show_home_button: bool,
}
#[swift_bridge(swift_repr = "struct")]
pub struct TabBar {
pub color: u32,
pub selected_color: u32,
pub background_color: u32,
pub border_style: u32,
pub position: i32,
pub dimension: i32,
pub items_count: i32,
pub is_visible: bool,
pub is_api_hidden: bool,
pub selected_index: i32,
pub styled_mask: u32,
}
#[swift_bridge(swift_repr = "struct")]
pub struct TabBarItem {
pub page_path: String,
pub text: String,
pub icon_path: String,
pub selected_icon_path: String,
pub selected: bool,
pub badge: String,
pub has_red_dot: bool,
}
pub enum PushTrigger {
Background,
Tap,
Launch,
}
pub enum LxAppUiEventType {
TabBarClick,
CapsuleClick,
NavigationClick,
BackPress,
PullDownRefresh,
}
pub enum AppUiEventType {
PanelIconClick,
UpdateRestartClick,
UpdateInstallClick,
TrayClick,
TrayMenuClick,
}
#[swift_bridge(swift_repr = "struct")]
pub struct CurrentLxApp {
pub appid: String,
pub path: String,
pub session_id: u64,
}
#[swift_bridge(swift_repr = "struct")]
pub struct CreatePageInstanceResult {
pub ok: bool,
pub page_instance_id: String,
pub appid: String,
pub resolved_path: String,
pub query: String,
pub error: String,
}
#[swift_bridge(swift_repr = "struct")]
pub struct PageBindingResult {
pub page_instance_id: String,
pub webview_ptr: usize,
}
#[swift_bridge(swift_repr = "struct")]
pub struct LingxiaInitResult {
pub ok: bool,
pub home_app_id: String,
pub error: String,
}
extern "Rust" {
#[swift_bridge(swift_name = "lingxiaInit")]
fn lingxia_init(data_dir: &str, cache_dir: &str, locale: &str) -> LingxiaInitResult;
#[swift_bridge(swift_name = "getDisplayLanguage")]
fn get_display_language() -> String;
#[swift_bridge(swift_name = "forwardHostLog")]
fn forward_host_log(
level: i32,
category: &str,
appid: &str,
path: &str,
message: &str,
) -> bool;
#[swift_bridge(swift_name = "setLogLevel")]
fn set_log_level(level: i32);
#[swift_bridge(swift_name = "hostLogEnabled")]
fn host_log_enabled(level: i32) -> bool;
#[swift_bridge(swift_name = "onPageShow")]
fn on_page_show(appid: &str, path: &str);
#[swift_bridge(swift_name = "onLxappClosed")]
fn on_lxapp_closed(appid: &str, session_id: u64) -> bool;
#[swift_bridge(swift_name = "onDeviceOrientationChanged")]
fn on_device_orientation_changed(appid: &str, session_id: u64, value: &str) -> bool;
#[swift_bridge(swift_name = "getLxAppInfo")]
fn get_lxapp_info(appid: &str) -> LxAppInfo;
#[swift_bridge(swift_name = "getNavigationBarState")]
fn get_navigation_bar_state(appid: &str, path: &str) -> NavigationBarState;
#[swift_bridge(swift_name = "getPageOrientation")]
fn get_page_orientation(appid: &str, path: &str) -> i32;
#[swift_bridge(swift_name = "getTabBar")]
fn get_tab_bar(appid: &str) -> Option<TabBar>;
#[swift_bridge(swift_name = "getTabBarItem")]
fn get_tab_bar_item(appid: &str, index: i32) -> Option<TabBarItem>;
#[swift_bridge(swift_name = "onLxappEvent")]
fn on_lxapp_event(appid: &str, event_type: LxAppUiEventType, data: &str) -> bool;
#[swift_bridge(swift_name = "onAppEvent")]
fn on_app_event(event_type: AppUiEventType, data: &str) -> bool;
#[swift_bridge(swift_name = "findWebViewByPageInstanceId")]
fn find_webview_by_page_instance_id(page_instance_id: &str) -> usize;
#[swift_bridge(swift_name = "resolvePageBinding")]
fn resolve_page_binding(appid: &str, path: &str, session_id: u64) -> PageBindingResult;
#[swift_bridge(swift_name = "createPageInstance")]
fn create_page_instance_for_open(
appid: &str,
path: &str,
session_id: u64,
surface: i32,
owner_page_instance_id: &str,
) -> CreatePageInstanceResult;
#[swift_bridge(swift_name = "isLxappRestartClosing")]
fn is_lxapp_restart_closing(appid: &str, session_id: u64) -> bool;
#[swift_bridge(swift_name = "notifyPageInstanceMounted")]
fn notify_page_instance_mounted(page_instance_id: &str) -> bool;
#[swift_bridge(swift_name = "notifyPageInstanceVisible")]
fn notify_page_instance_visible(page_instance_id: &str) -> bool;
#[swift_bridge(swift_name = "notifyPageInstanceHidden")]
fn notify_page_instance_hidden(page_instance_id: &str, reason: &str) -> bool;
#[swift_bridge(swift_name = "disposePageInstance")]
fn dispose_page_instance(page_instance_id: &str, reason: &str) -> bool;
#[swift_bridge(swift_name = "onSurfaceClosed")]
fn on_surface_closed(appid: &str, id: &str, reason: &str) -> bool;
#[swift_bridge(swift_name = "setSurfaceWidth")]
fn set_surface_width(appid: &str, width: f64) -> bool;
#[swift_bridge(swift_name = "setSurfaceLayoutMetrics")]
fn set_surface_layout_metrics(appid: &str, width: f64, sidebar_width: f64) -> bool;
#[swift_bridge(swift_name = "setSurfaceViewport")]
fn set_surface_viewport(appid: &str, width: f64, height: f64) -> bool;
#[swift_bridge(swift_name = "surfaceDerivedLayout")]
fn surface_derived_layout(appid: &str) -> String;
#[swift_bridge(swift_name = "setActiveMain")]
fn set_active_main(appid: &str) -> bool;
#[swift_bridge(swift_name = "focusSurface")]
fn focus_surface(appid: &str, surface_id: &str) -> bool;
#[swift_bridge(swift_name = "shellActivate")]
fn shell_activate(item_id: &str) -> bool;
#[swift_bridge(swift_name = "shellOpenLxappMain")]
fn shell_open_lxapp_main(app_id: &str) -> bool;
#[swift_bridge(swift_name = "shellPins")]
fn shell_pins() -> String;
#[swift_bridge(swift_name = "shellIsPinned")]
fn shell_is_pinned(kind: &str, key: &str) -> bool;
#[swift_bridge(swift_name = "shellSetPinned")]
fn shell_set_pinned(kind: &str, key: &str, pinned: bool) -> i32;
#[swift_bridge(swift_name = "registerHostAside")]
fn register_host_aside(appid: &str, surface_id: &str, edge: &str) -> bool;
#[swift_bridge(swift_name = "registerHostAsideContent")]
fn register_host_aside_content(
appid: &str,
surface_id: &str,
content_id: &str,
edge: &str,
) -> bool;
#[swift_bridge(swift_name = "unregisterHostAside")]
fn unregister_host_aside(appid: &str, surface_id: &str) -> bool;
#[swift_bridge(swift_name = "openBrowserTab")]
fn open_browser_tab(appid: &str, session_id: u64, url: &str) -> Option<String>;
#[swift_bridge(swift_name = "openUnownedBrowserTab")]
fn open_unowned_browser_tab(url: &str) -> Option<String>;
#[swift_bridge(swift_name = "configureAppleUserAgentOverride")]
fn configure_apple_user_agent_override(
use_default: bool,
user_agent: &str,
reload_existing: bool,
) -> bool;
#[swift_bridge(swift_name = "openStandaloneBrowserTab")]
fn open_standalone_browser_tab(
appid: &str,
session_id: u64,
url: &str,
ephemeral_web_data: bool,
url_callback: bool,
) -> Option<String>;
#[swift_bridge(swift_name = "openAsideBrowserTab")]
fn open_aside_browser_tab(appid: &str, session_id: u64, url: &str) -> Option<String>;
#[swift_bridge(swift_name = "browserTabIsAside")]
fn browser_tab_is_aside(tab_id: &str) -> bool;
#[swift_bridge(swift_name = "browserTabIdsJson")]
fn browser_tab_ids_json() -> String;
#[swift_bridge(swift_name = "browserCurrentTabId")]
fn browser_current_tab_id() -> String;
#[swift_bridge(swift_name = "urlCallbackDispatch")]
fn url_callback_dispatch(url: &str) -> bool;
#[swift_bridge(swift_name = "webviewLoadErrorDocument")]
fn webview_load_error_document(url: &str) -> String;
#[swift_bridge(swift_name = "openBrowserTabWithId")]
fn open_browser_tab_with_id(
appid: &str,
session_id: u64,
url: &str,
tab_id: &str,
) -> Option<String>;
#[swift_bridge(swift_name = "browserTabClose")]
fn browser_tab_close(tab_id: &str) -> bool;
#[swift_bridge(swift_name = "browserTabNavigate")]
fn browser_tab_navigate(tab_id: &str, url: &str) -> bool;
#[swift_bridge(swift_name = "browserTabDiscard")]
fn browser_tab_discard(tab_id: &str) -> bool;
#[swift_bridge(swift_name = "browserTabReactivate")]
fn browser_tab_reactivate(tab_id: &str) -> bool;
#[swift_bridge(swift_name = "browserTabActivate")]
fn browser_tab_activate(tab_id: &str);
#[swift_bridge(swift_name = "browserTabDeactivate")]
fn browser_tab_deactivate();
#[swift_bridge(swift_name = "getBuiltinBrowserAppId")]
fn get_builtin_browser_app_id() -> String;
#[swift_bridge(swift_name = "browserTabPathForId")]
fn browser_tab_path_for_id(tab_id: &str) -> String;
#[swift_bridge(swift_name = "updateBrowserTabInfo")]
fn update_browser_tab_info(tab_id: &str, current_url: &str, title: &str) -> bool;
#[swift_bridge(swift_name = "browserBookmarkStatus")]
fn browser_bookmark_status(url: &str) -> bool;
#[swift_bridge(swift_name = "browserBookmarkState")]
fn browser_bookmark_state(url: &str) -> u32;
#[swift_bridge(swift_name = "browserBookmarkNormalizeUrl")]
fn browser_bookmark_normalize_url(raw: &str) -> String;
#[swift_bridge(swift_name = "browserBookmarkToggle")]
fn browser_bookmark_toggle(url: &str, title: &str) -> bool;
#[swift_bridge(swift_name = "browserBookmarkRemoveByUrl")]
fn browser_bookmark_remove_by_url(url: &str) -> bool;
#[swift_bridge(swift_name = "browserBookmarksSnapshotJson")]
fn browser_bookmarks_snapshot_json() -> String;
#[swift_bridge(swift_name = "browserBookmarkFaviconPath")]
fn browser_bookmark_favicon_path(url: &str) -> String;
#[swift_bridge(swift_name = "browserFaviconStore")]
fn browser_favicon_store(url: &str, bytes: &[u8]) -> bool;
#[swift_bridge(swift_name = "browserBookmarksCommand")]
fn browser_bookmarks_command(json: &str) -> bool;
#[swift_bridge(swift_name = "browserBookmarkPin")]
fn browser_bookmark_pin(url: &str, title: &str) -> bool;
#[swift_bridge(swift_name = "startBrowserTabDownload")]
fn start_browser_tab_download(
tab_id: &str,
url: &str,
user_agent: &str,
suggested_filename: &str,
source_page_url: &str,
cookie: &str,
) -> bool;
#[swift_bridge(swift_name = "toggleWebViewDevtoolsByPtr")]
fn toggle_webview_devtools_by_ptr(webview_ptr: usize, detached: bool) -> bool;
#[swift_bridge(swift_name = "onApplinkReceived")]
fn on_applink_received(applink_path: &str) -> i32;
#[swift_bridge(swift_name = "getCurrentLxApp")]
fn get_current_lxapp() -> CurrentLxApp;
#[swift_bridge(swift_name = "getLxAppSessionId")]
fn get_lxapp_session_id(appid: &str) -> u64;
#[swift_bridge(swift_name = "onPushlinkReceived")]
fn on_pushlink_received(url: &str, trigger: PushTrigger) -> i32;
#[swift_bridge(swift_name = "onPushTokenReceived")]
fn on_push_token_received(token: &str) -> i32;
#[swift_bridge(swift_name = "onCallback")]
fn on_callback(id: u64, success: bool, data: &str) -> bool;
#[swift_bridge(swift_name = "onNativeComponentEvent")]
fn on_native_component_event(
appid: &str,
path: &str,
component_id: &str,
event_name: &str,
payload_json: &str,
bindings_json: &str,
) -> bool;
#[swift_bridge(swift_name = "isPullDownRefreshEnabled")]
fn is_pull_down_refresh_enabled(appid: &str, path: &str) -> bool;
#[swift_bridge(swift_name = "resolveLxUri")]
fn resolve_lx_uri(appid: &str, input: &str) -> Option<String>;
#[swift_bridge(swift_name = "handleBrowserAddressInput")]
fn handle_browser_address_input(request_json: &str) -> Option<String>;
#[swift_bridge(swift_name = "browserUrlIsHidden")]
fn browser_url_is_hidden(raw: &str) -> bool;
#[swift_bridge(swift_name = "onAppShow")]
fn on_app_show(lxappid: &str);
#[swift_bridge(swift_name = "onAppHide")]
fn on_app_hide(lxappid: &str);
#[swift_bridge(swift_name = "onUserCaptureScreen")]
fn on_user_capture_screen(lxappid: &str);
#[swift_bridge(swift_name = "getAppCapabilities")]
fn get_app_capabilities() -> u32;
#[swift_bridge(swift_name = "shouldEnableWebViewDebugging")]
fn should_enable_webview_debugging() -> bool;
#[swift_bridge(swift_name = "getPanelsConfigJson")]
fn get_panels_config_json() -> Option<String>;
#[swift_bridge(swift_name = "openPanelLxapp")]
fn open_panel_lxapp(panel_id: &str, appid: &str, path: &str);
#[swift_bridge(swift_name = "getTerminalBackendStatusJson")]
fn get_terminal_backend_status_json() -> String;
#[swift_bridge(swift_name = "terminalSessionCreate")]
fn terminal_session_create(cols: u16, rows: u16) -> u64;
#[swift_bridge(swift_name = "terminalSessionWrite")]
fn terminal_session_write(id: u64, input: &str) -> bool;
#[swift_bridge(swift_name = "terminalSessionRead")]
fn terminal_session_read(id: u64) -> String;
#[swift_bridge(swift_name = "terminalSessionSnapshot")]
fn terminal_session_snapshot(id: u64) -> String;
#[swift_bridge(swift_name = "terminalSessionExited")]
fn terminal_session_exited(id: u64) -> bool;
#[swift_bridge(swift_name = "terminalSessionResize")]
fn terminal_session_resize(id: u64, cols: u16, rows: u16) -> bool;
#[swift_bridge(swift_name = "terminalSessionScroll")]
fn terminal_session_scroll(
id: u64,
delta_rows: i32,
col: u16,
row: u16,
allow_application_input: bool,
) -> bool;
#[swift_bridge(swift_name = "terminalSessionClose")]
fn terminal_session_close(id: u64);
}
extern "Swift" {
#[swift_bridge(swift_name = "LxApp.presentInternalBrowserTab")]
fn present_internal_browser_tab(tab_id: &str) -> bool;
#[swift_bridge(swift_name = "LxApp.prepareInternalBrowserTabForInput")]
fn prepare_internal_browser_tab_for_input(tab_id: &str) -> bool;
#[swift_bridge(swift_name = "LxApp.browserBookmarksChanged")]
fn browser_bookmarks_changed();
#[swift_bridge(swift_name = "LxApp.displayLanguageChanged")]
fn display_language_changed();
}
}
#[cfg(all(target_os = "macos", feature = "browser-shell"))]
struct AppleBrowserNativeInputHost;
#[cfg(all(target_os = "macos", feature = "browser-shell"))]
impl lingxia_browser::BrowserNativeInputHost for AppleBrowserNativeInputHost {
fn prepare_for_input(&self, tab_id: &str) -> Result<(), String> {
if self::bridge::prepare_internal_browser_tab_for_input(tab_id) {
Ok(())
} else {
Err(format!("failed to prepare browser tab for input: {tab_id}"))
}
}
}
#[cfg(all(target_os = "macos", feature = "browser-shell"))]
fn install_browser_native_input_host() {
let _ = lingxia_browser::register_native_input_host(Arc::new(AppleBrowserNativeInputHost));
lingxia_browser_shell::set_bookmarks_change_listener(Box::new(|| {
self::bridge::browser_bookmarks_changed();
}));
lingxia_browser_shell::set_display_language_change_listener(Box::new(|| {
self::bridge::display_language_changed();
}));
}
#[cfg(not(all(target_os = "macos", feature = "browser-shell")))]
fn install_browser_native_input_host() {}
pub fn lingxia_init(data_dir: &str, cache_dir: &str, locale: &str) -> bridge::LingxiaInitResult {
crate::logging::init();
install_browser_native_input_host();
log::info!(
"Initializing Lingxia SDK with data_dir: {}, cache_dir: {}",
data_dir,
cache_dir
);
let platform = match lingxia_platform::Platform::new(
data_dir.to_string(),
cache_dir.to_string(),
locale.to_string(),
) {
Ok(platform) => platform,
Err(e) => {
log::error!("Failed to create Platform: {}", e);
return bridge::LingxiaInitResult {
ok: false,
home_app_id: String::new(),
error: e.to_string(),
};
}
};
match crate::init_with_platform(platform) {
Ok(info) => bridge::LingxiaInitResult {
ok: true,
home_app_id: info.into_lxapp_id().unwrap_or_default(),
error: String::new(),
},
Err(error) => bridge::LingxiaInitResult {
ok: false,
home_app_id: String::new(),
error: error.to_string(),
},
}
}
pub fn get_display_language() -> String {
crate::app::display_language()
}
pub fn forward_host_log(
level: i32,
category: &str,
appid: &str,
path: &str,
message: &str,
) -> bool {
crate::logging::forward_host_log(level, category, appid, path, message)
}
pub fn set_log_level(level: i32) {
crate::logging::set_log_level(level);
}
pub fn host_log_enabled(level: i32) -> bool {
crate::logging::host_log_enabled(level)
}
pub fn on_page_show(appid: &str, path: &str) {
if let Some(lxapp) = lxapp::try_get(appid) {
lxapp.on_page_show(path.to_string());
}
}
pub fn resolve_lx_uri(appid: &str, input: &str) -> Option<String> {
let trimmed = input.trim();
if trimmed.is_empty() {
return None;
}
if trimmed.starts_with("http://") || trimmed.starts_with("https://") {
return Some(trimmed.to_string());
}
let lxapp = lxapp::try_get(appid)?;
if let Some(path) = trimmed.strip_prefix("file://") {
let resolved = lxapp.resolve_accessible_path(path).ok()?;
return Some(format!("file://{}", resolved.to_string_lossy()));
}
let resolved = lxapp.resolve_accessible_path(trimmed).ok()?;
Some(format!("file://{}", resolved.to_string_lossy()))
}
pub fn handle_browser_address_input(request_json: &str) -> Option<String> {
crate::browser::resolve_input_json(request_json)
}
pub fn browser_url_is_hidden(raw: &str) -> bool {
crate::browser::should_hide_url(raw)
}
macro_rules! ffi_catch_unwind {
($name:expr, $default:expr, $body:expr) => {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe($body)) {
Ok(v) => v,
Err(_) => {
log::error!("panic in {}", $name);
$default
}
}
};
}
pub fn on_lxapp_closed(appid: &str, session_id: u64) -> bool {
ffi_catch_unwind!("on_lxapp_closed", false, || {
if let Some(lxapp) = lxapp::try_get(appid) {
if session_id == 0 || session_id != lxapp.session_id() {
return false;
}
lxapp.on_lxapp_closed(session_id);
return true;
}
false
})
}
pub fn on_device_orientation_changed(appid: &str, session_id: u64, value: &str) -> bool {
let Some(lxapp) = lxapp::try_get(appid) else {
return false;
};
if session_id == 0 || session_id != lxapp.session_id() {
return false;
}
let normalized = match value {
"portrait" => "portrait",
"landscape" => "landscape",
_ => return false,
};
let payload = format!(r#"{{"value":"{}"}}"#, normalized);
lxapp::publish_app_event(appid, "DeviceOrientationChange", Some(payload))
}
pub fn on_lxapp_event(appid: &str, event_type: self::bridge::LxAppUiEventType, data: &str) -> bool {
let ui_event_type = match event_type {
self::bridge::LxAppUiEventType::TabBarClick => LxAppUiEventType::TabBarClick,
self::bridge::LxAppUiEventType::CapsuleClick => LxAppUiEventType::CapsuleClick,
self::bridge::LxAppUiEventType::NavigationClick => LxAppUiEventType::NavigationClick,
self::bridge::LxAppUiEventType::BackPress => LxAppUiEventType::BackPress,
self::bridge::LxAppUiEventType::PullDownRefresh => LxAppUiEventType::PullDownRefresh,
};
lxapp::try_get(appid)
.map(|lxapp| lxapp.on_lxapp_event(ui_event_type, data.to_string()))
.unwrap_or(false)
}
pub fn on_app_event(event_type: self::bridge::AppUiEventType, data: &str) -> bool {
match event_type {
self::bridge::AppUiEventType::PanelIconClick => {
if let Some((app_id, path)) = crate::browser::panel_item_for_id(data) {
crate::browser::open_panel_lxapp(data, &app_id, &path);
true
} else {
false
}
}
self::bridge::AppUiEventType::UpdateRestartClick => {
lingxia_platform::apply_staged_macos_update()
}
self::bridge::AppUiEventType::UpdateInstallClick => {
crate::task::spawn(async {
let _ = crate::update::host_app::check().await;
});
true
}
self::bridge::AppUiEventType::TrayClick => {
if !data.is_empty() {
lxapp::publish_app_event(data, "lx.tray.click", None);
}
true
}
self::bridge::AppUiEventType::TrayMenuClick => {
if let Some((appid, index)) = data.rsplit_once(':')
&& !appid.is_empty()
{
let event = format!("lx.tray.menu:{index}");
lxapp::publish_app_event(appid, &event, None);
}
true
}
}
}
pub fn on_native_component_event(
appid: &str,
path: &str,
component_id: &str,
event_name: &str,
payload_json: &str,
bindings_json: &str,
) -> bool {
lxapp::on_native_component_event(
appid,
path,
component_id,
event_name,
payload_json,
bindings_json,
)
}
pub fn open_browser_tab(appid: &str, session_id: u64, url: &str) -> Option<String> {
ffi_catch_unwind!("open_browser_tab", None, || {
match crate::browser::open_for_app(appid, session_id, url, None) {
Ok(tab_id) => Some(tab_id),
Err(e) => {
log::error!("open_browser_tab failed: {}", e);
None
}
}
})
}
pub fn open_unowned_browser_tab(url: &str) -> Option<String> {
ffi_catch_unwind!("open_unowned_browser_tab", None, || {
match crate::browser::open(url, None) {
Ok(tab_id) => Some(tab_id),
Err(error) => {
log::error!("open_unowned_browser_tab failed: {error}");
None
}
}
})
}
pub fn configure_apple_user_agent_override(
use_default: bool,
user_agent: &str,
reload_existing: bool,
) -> bool {
ffi_catch_unwind!("configure_apple_user_agent_override", false, || {
let user_agent = if use_default {
lingxia_webview::UserAgentOverride::Default
} else {
lingxia_webview::UserAgentOverride::Custom(user_agent.to_string())
};
lingxia_webview::platform::apple::configure_user_agent_override_for_webviews(
user_agent,
true,
reload_existing,
)
.map_err(|err| log::error!("configure Apple user agent failed: {err}"))
.is_ok()
})
}
pub fn url_callback_dispatch(url: &str) -> bool {
ffi_catch_unwind!("url_callback_dispatch", false, || {
lingxia_webview::url_callback::dispatch(url)
})
}
pub fn webview_load_error_document(url: &str) -> String {
crate::webview_error::load_error_document(url)
}
pub fn open_standalone_browser_tab(
appid: &str,
session_id: u64,
url: &str,
ephemeral_web_data: bool,
url_callback: bool,
) -> Option<String> {
ffi_catch_unwind!("open_standalone_browser_tab", None, || {
let data_mode = if ephemeral_web_data {
lingxia_webview::WebViewDataMode::Ephemeral
} else {
lingxia_webview::WebViewDataMode::ProfileDefault
};
match crate::browser::open_standalone_for_app(
appid,
session_id,
url,
None,
data_mode,
url_callback,
) {
Ok(tab_id) => Some(tab_id),
Err(e) => {
log::error!("open_standalone_browser_tab failed: {}", e);
None
}
}
})
}
pub fn open_aside_browser_tab(appid: &str, session_id: u64, url: &str) -> Option<String> {
ffi_catch_unwind!("open_aside_browser_tab", None, || {
match crate::browser::open_aside_for_app(appid, session_id, url, None) {
Ok(tab_id) => Some(tab_id),
Err(e) => {
log::error!("open_aside_browser_tab failed: {}", e);
None
}
}
})
}
pub fn browser_tab_is_aside(tab_id: &str) -> bool {
ffi_catch_unwind!("browser_tab_is_aside", false, || {
crate::browser::tab_is_aside(tab_id)
})
}
pub fn browser_tab_ids_json() -> String {
ffi_catch_unwind!("browser_tab_ids_json", "[]".to_string(), || {
#[cfg(feature = "browser-runtime")]
{
let ids = lingxia_browser::tabs()
.into_iter()
.filter(|tab| {
!lingxia_browser::tab_is_aside(&tab.tab_id)
&& !lingxia_browser::tab_is_standalone(&tab.tab_id)
})
.map(|tab| tab.tab_id)
.collect::<Vec<_>>();
serde_json::to_string(&ids).unwrap_or_else(|_| "[]".to_string())
}
#[cfg(not(feature = "browser-runtime"))]
"[]".to_string()
})
}
pub fn browser_current_tab_id() -> String {
ffi_catch_unwind!("browser_current_tab_id", String::new(), || {
#[cfg(feature = "browser-runtime")]
{
lingxia_browser::current_tab()
.filter(|tab| {
!lingxia_browser::tab_is_aside(&tab.tab_id)
&& !lingxia_browser::tab_is_standalone(&tab.tab_id)
})
.map(|tab| tab.tab_id)
.unwrap_or_default()
}
#[cfg(not(feature = "browser-runtime"))]
String::new()
})
}
pub fn open_browser_tab_with_id(
appid: &str,
session_id: u64,
url: &str,
tab_id: &str,
) -> Option<String> {
ffi_catch_unwind!("open_browser_tab_with_id", None, || {
match crate::browser::open_for_app(appid, session_id, url, Some(tab_id)) {
Ok(tab_id) => Some(tab_id),
Err(e) => {
log::error!("open_browser_tab_with_id failed: {}", e);
None
}
}
})
}
pub fn browser_tab_close(tab_id: &str) -> bool {
ffi_catch_unwind!("browser_tab_close", false, || {
crate::browser::close(tab_id).is_ok()
})
}
pub fn browser_tab_navigate(tab_id: &str, url: &str) -> bool {
ffi_catch_unwind!("browser_tab_navigate", false, || {
crate::browser::navigate(tab_id, url).is_ok()
})
}
pub fn browser_tab_discard(tab_id: &str) -> bool {
ffi_catch_unwind!("browser_tab_discard", false, || {
crate::browser::discard(tab_id).is_ok()
})
}
pub fn browser_tab_reactivate(tab_id: &str) -> bool {
ffi_catch_unwind!("browser_tab_reactivate", false, || {
crate::browser::reactivate(tab_id).is_ok()
})
}
pub fn browser_tab_activate(tab_id: &str) {
ffi_catch_unwind!("browser_tab_activate", (), || {
crate::browser::mark_active(tab_id)
})
}
pub fn browser_tab_deactivate() {
ffi_catch_unwind!("browser_tab_deactivate", (), || {
crate::browser::clear_active()
})
}
pub fn get_builtin_browser_app_id() -> String {
crate::browser::APP_ID.to_string()
}
pub fn browser_bookmark_status(url: &str) -> bool {
ffi_catch_unwind!("browser_bookmark_status", false, || {
crate::browser::bookmark_status(url)
})
}
pub fn browser_bookmark_state(url: &str) -> u32 {
ffi_catch_unwind!("browser_bookmark_state", 0, || {
crate::browser::bookmark_state(url)
})
}
pub fn browser_bookmark_normalize_url(raw: &str) -> String {
ffi_catch_unwind!("browser_bookmark_normalize_url", String::new(), || {
crate::browser::normalize_bookmark_url(raw)
})
}
pub fn browser_bookmark_toggle(url: &str, title: &str) -> bool {
ffi_catch_unwind!("browser_bookmark_toggle", false, || {
crate::browser::bookmark_toggle(url, title)
})
}
pub fn browser_bookmark_remove_by_url(url: &str) -> bool {
ffi_catch_unwind!("browser_bookmark_remove_by_url", false, || {
crate::browser::bookmark_remove_by_url(url)
})
}
pub fn browser_bookmarks_snapshot_json() -> String {
ffi_catch_unwind!("browser_bookmarks_snapshot_json", String::new(), || {
crate::browser::bookmarks_snapshot_json()
})
}
pub fn browser_bookmark_favicon_path(url: &str) -> String {
ffi_catch_unwind!("browser_bookmark_favicon_path", String::new(), || {
crate::browser::bookmark_favicon_path(url)
})
}
pub fn browser_favicon_store(url: &str, bytes: &[u8]) -> bool {
ffi_catch_unwind!("browser_favicon_store", false, || {
crate::browser::store_favicon(url, bytes)
})
}
pub fn browser_bookmarks_command(json: &str) -> bool {
ffi_catch_unwind!("browser_bookmarks_command", false, || {
crate::browser::bookmarks_command_json(json)
})
}
pub fn browser_bookmark_pin(url: &str, title: &str) -> bool {
ffi_catch_unwind!("browser_bookmark_pin", false, || {
crate::browser::bookmark_pin(url, title)
})
}
pub fn browser_tab_path_for_id(tab_id: &str) -> String {
crate::browser::tab_path(tab_id)
}
pub fn present_internal_browser_tab(tab_id: &str) -> bool {
ffi_catch_unwind!("present_internal_browser_tab", false, || {
self::bridge::present_internal_browser_tab(tab_id)
})
}
pub fn set_surface_width(appid: &str, width: f64) -> bool {
ffi_catch_unwind!("set_surface_width", false, || {
lxapp::try_get(appid)
.map(|lxapp| lxapp.set_surface_width(width))
.unwrap_or(false)
})
}
pub fn set_surface_layout_metrics(appid: &str, width: f64, sidebar_width: f64) -> bool {
ffi_catch_unwind!("set_surface_layout_metrics", false, || {
lxapp::try_get(appid)
.map(|lxapp| lxapp.set_surface_layout_metrics(width, sidebar_width))
.unwrap_or(false)
})
}
pub fn set_surface_viewport(appid: &str, width: f64, height: f64) -> bool {
ffi_catch_unwind!("set_surface_viewport", false, || {
lxapp::try_get(appid)
.map(|lxapp| lxapp.set_surface_viewport(width, height))
.unwrap_or(false)
})
}
pub fn surface_derived_layout(appid: &str) -> String {
ffi_catch_unwind!("surface_derived_layout", "null".to_string(), || {
lxapp::try_get(appid)
.and_then(|lxapp| lxapp.surface_derived_layout())
.and_then(|layout| serde_json::to_string(&layout).ok())
.unwrap_or_else(|| "null".to_string())
})
}
pub fn set_active_main(appid: &str) -> bool {
ffi_catch_unwind!("set_active_main", false, || {
if let Some(lxapp) = lxapp::try_get(appid) {
lxapp.set_active_main();
true
} else {
false
}
})
}
pub fn shell_activate(item_id: &str) -> bool {
ffi_catch_unwind!("shell_activate", false, || {
lingxia_shell::activate(item_id).is_ok()
})
}
pub fn shell_open_lxapp_main(app_id: &str) -> bool {
ffi_catch_unwind!("shell_open_lxapp_main", false, || {
let app_id = app_id.trim().to_string();
if app_id.is_empty() {
return false;
}
if lxapp::open_region(&app_id) == Some(lxapp::LxAppOpenRegion::Aside) {
log::warn!("pin open refused: {app_id} is open as an aside");
return false;
}
std::mem::drop(rong_rt::RongExecutor::global().spawn(async move {
if let Err(err) = lxapp::prepare_lxapp_open(&app_id, lxapp::ReleaseType::Release).await
{
log::error!("pin open failed for {app_id}: {err}");
return;
}
if let Err(err) = lxapp::open_lxapp(&app_id, lxapp::LxAppStartupOptions::default()) {
log::error!("pin open failed for {app_id}: {err}");
return;
}
lxapp::schedule_lxapp_update_check(&app_id, lxapp::ReleaseType::Release);
}));
true
})
}
pub fn shell_pins() -> String {
ffi_catch_unwind!("shell_pins", String::new(), || {
lingxia_shell::pins()
.ok()
.map(|pins| crate::shell::visible_shell_pins(&pins, lingxia_app_context::home_app_id()))
.and_then(|pins| serde_json::to_string(&pins).ok())
.unwrap_or_default()
})
}
pub fn shell_is_pinned(kind: &str, key: &str) -> bool {
ffi_catch_unwind!("shell_is_pinned", false, || {
if shell_pin_is_home_lxapp(kind, key) {
return false;
}
shell_pin_target(kind, key)
.and_then(|target| lingxia_shell::is_pinned(&target).ok())
.unwrap_or(false)
})
}
pub fn shell_set_pinned(kind: &str, key: &str, pinned: bool) -> i32 {
ffi_catch_unwind!("shell_set_pinned", 0, || {
if pinned && shell_pin_is_home_lxapp(kind, key) {
return 0;
}
let Some(target) = shell_pin_target(kind, key) else {
return 0;
};
match lingxia_shell::set_pinned(target, pinned) {
Ok(_) => 1,
Err(lingxia_shell::ShellError::LimitReached { .. }) => -1,
Err(error) => {
log::warn!("failed to update shell Pin: {error}");
0
}
}
})
}
fn shell_pin_is_home_lxapp(kind: &str, key: &str) -> bool {
kind == "lxapp" && lingxia_app_context::home_app_id().is_some_and(|home| home == key.trim())
}
fn shell_pin_target(kind: &str, key: &str) -> Option<lingxia_shell::ShellPinTarget> {
let key = key.trim();
if key.is_empty() {
return None;
}
match kind {
"lxapp" => Some(lingxia_shell::ShellPinTarget::Lxapp {
key: key.to_string(),
}),
"bookmark" => Some(lingxia_shell::ShellPinTarget::Bookmark {
key: key.to_string(),
}),
_ => None,
}
}
pub fn focus_surface(appid: &str, surface_id: &str) -> bool {
ffi_catch_unwind!("focus_surface", false, || {
if let Some(lxapp) = lxapp::try_get(appid) {
lxapp.focus_shell_surface(surface_id)
} else {
false
}
})
}
pub fn register_host_aside(appid: &str, surface_id: &str, edge: &str) -> bool {
ffi_catch_unwind!("register_host_aside", false, || {
if let Some(lxapp) = lxapp::try_get(appid) {
lxapp.register_host_aside(surface_id, edge);
true
} else {
false
}
})
}
pub fn register_host_aside_content(
appid: &str,
surface_id: &str,
content_id: &str,
edge: &str,
) -> bool {
ffi_catch_unwind!("register_host_aside_content", false, || {
if let Some(lxapp) = lxapp::try_get(appid) {
lxapp.register_host_aside_content(surface_id, content_id, edge);
true
} else {
false
}
})
}
pub fn unregister_host_aside(appid: &str, surface_id: &str) -> bool {
ffi_catch_unwind!("unregister_host_aside", false, || {
if let Some(lxapp) = lxapp::try_get(appid) {
lxapp.unregister_host_aside(surface_id);
true
} else {
false
}
})
}
pub fn update_browser_tab_info(tab_id: &str, current_url: &str, title: &str) -> bool {
ffi_catch_unwind!("update_browser_tab_info", false, || {
let current_url = if current_url.trim().is_empty() {
None
} else {
Some(current_url)
};
let title = if title.trim().is_empty() {
None
} else {
Some(title)
};
crate::browser::update_tab(tab_id, current_url, title)
})
}
pub fn start_browser_tab_download(
tab_id: &str,
url: &str,
user_agent: &str,
suggested_filename: &str,
source_page_url: &str,
cookie: &str,
) -> bool {
ffi_catch_unwind!("start_browser_tab_download", false, || {
match crate::browser::download(
tab_id,
url,
Some(user_agent),
Some(suggested_filename),
Some(source_page_url),
Some(cookie),
) {
Ok(()) => {
log::info!(
"start_browser_tab_download accepted tab_id={} url={}",
tab_id,
url
);
true
}
Err(err) => {
log::warn!(
"start_browser_tab_download failed tab_id={} url={} error={}",
tab_id,
url,
err
);
false
}
}
})
}
pub fn toggle_webview_devtools_by_ptr(webview_ptr: usize, detached: bool) -> bool {
if webview_ptr == 0 {
return false;
}
#[cfg(target_os = "macos")]
{
lingxia_webview::platform::apple::toggle_webview_devtools_by_swift_ptr(
webview_ptr,
detached,
)
}
#[cfg(not(target_os = "macos"))]
{
let _ = detached;
false
}
}
pub fn get_current_lxapp() -> self::bridge::CurrentLxApp {
let (current_appid, current_path, current_session_id) = lxapp::get_current_lxapp();
self::bridge::CurrentLxApp {
appid: current_appid,
path: current_path,
session_id: current_session_id,
}
}
pub fn get_lxapp_session_id(appid: &str) -> u64 {
lxapp::try_get(appid)
.map(|lxapp| lxapp.session_id())
.unwrap_or(0)
}
pub fn find_webview_by_page_instance_id(page_instance_id: &str) -> usize {
let page_instance_id = page_instance_id.trim();
if page_instance_id.is_empty() {
return 0;
}
let _ = lxapp::touch_page_instance_by_id(page_instance_id);
lxapp::find_page_by_instance_id(page_instance_id)
.and_then(|page| page.webview())
.map(|webview| webview.get_swift_webview_ptr())
.unwrap_or(0)
}
fn resolve_page_instance_id(appid: &str, path: &str, session_id: u64) -> String {
if session_id == 0 {
return String::new();
}
fn normalize_lookup_path(path: &str) -> &str {
let path = path.split('?').next().unwrap_or(path);
path.split('#').next().unwrap_or(path)
}
let Some(lxapp) = lxapp::try_get(appid) else {
return String::new();
};
if lxapp.session_id() != session_id {
return String::new();
}
let normalized_path = normalize_lookup_path(path);
let resolved_path = lxapp
.find_page_path(normalized_path)
.unwrap_or_else(|| normalized_path.to_string());
let page_instance_id = lxapp
.page_instance_id_for_path(&resolved_path)
.unwrap_or_default();
if !page_instance_id.is_empty() {
let _ = lxapp::touch_page_instance_by_id(&page_instance_id);
}
page_instance_id
}
pub fn resolve_page_binding(
appid: &str,
path: &str,
session_id: u64,
) -> self::bridge::PageBindingResult {
let page_instance_id = resolve_page_instance_id(appid, path, session_id);
let mut webview_ptr = if page_instance_id.is_empty() {
0
} else {
find_webview_by_page_instance_id(&page_instance_id)
};
if webview_ptr == 0 {
let webtag = lingxia_webview::WebTag::new(appid, path, Some(session_id));
if let Some(webview) = lingxia_webview::runtime::find_webview(&webtag) {
webview_ptr = webview.get_swift_webview_ptr();
}
}
self::bridge::PageBindingResult {
page_instance_id,
webview_ptr,
}
}
pub fn is_lxapp_restart_closing(appid: &str, session_id: u64) -> bool {
lxapp::try_get(appid)
.map(|app| app.is_restart_closing_session(session_id))
.unwrap_or(false)
}
fn parse_close_reason(reason: &str) -> CloseReason {
match reason.trim().to_ascii_lowercase().as_str() {
"user" => CloseReason::User,
"owner_closed" => CloseReason::OwnerClosed,
"app_closed" | "appclose" | "close" => CloseReason::AppClosed,
"programmatic" => CloseReason::Programmatic,
"failed" | "presentation_failed" => CloseReason::Unknown,
_ => CloseReason::Unknown,
}
}
fn map_presentation_kind(surface: i32) -> PresentationKind {
match surface {
1 => PresentationKind::Panel,
3 => PresentationKind::Overlay,
_ => PresentationKind::Window,
}
}
pub fn create_page_instance_for_open(
appid: &str,
path: &str,
session_id: u64,
surface: i32,
owner_page_instance_id: &str,
) -> self::bridge::CreatePageInstanceResult {
if session_id == 0 {
return self::bridge::CreatePageInstanceResult {
ok: false,
page_instance_id: String::new(),
appid: appid.to_string(),
resolved_path: String::new(),
query: String::new(),
error: "invalid session_id".to_string(),
};
}
let Some(lxapp_instance) = lxapp::try_get(appid) else {
return self::bridge::CreatePageInstanceResult {
ok: false,
page_instance_id: String::new(),
appid: appid.to_string(),
resolved_path: String::new(),
query: String::new(),
error: "lxapp not found".to_string(),
};
};
if lxapp_instance.session_id() != session_id {
return self::bridge::CreatePageInstanceResult {
ok: false,
page_instance_id: String::new(),
appid: appid.to_string(),
resolved_path: String::new(),
query: String::new(),
error: "stale session".to_string(),
};
}
if lxapp_instance.is_restart_closing_session(session_id) {
return self::bridge::CreatePageInstanceResult {
ok: false,
page_instance_id: String::new(),
appid: appid.to_string(),
resolved_path: String::new(),
query: String::new(),
error: "restart closing session".to_string(),
};
}
let owner = if let Some(owner_id) = PageInstanceId::parse(owner_page_instance_id.to_string()) {
PageOwner::Page(owner_id)
} else {
PageOwner::Scene(SceneId("system".to_string()))
};
match lxapp::create_page_instance(CreatePageInstanceRequest {
owner,
appid: appid.to_string(),
target: PageTarget::Path(path.to_string()),
query: None::<PageQueryInput>,
surface: map_presentation_kind(surface),
}) {
Ok(created) => self::bridge::CreatePageInstanceResult {
ok: true,
page_instance_id: created.page_instance_id.to_string(),
appid: created.appid,
resolved_path: created.resolved_path,
query: created.query,
error: String::new(),
},
Err(err) => self::bridge::CreatePageInstanceResult {
ok: false,
page_instance_id: String::new(),
appid: appid.to_string(),
resolved_path: String::new(),
query: String::new(),
error: err.to_string(),
},
}
}
pub fn notify_page_instance_mounted(page_instance_id: &str) -> bool {
lxapp::notify_page_instance_by_id(page_instance_id, PageInstanceEvent::Mounted).is_ok()
}
pub fn notify_page_instance_visible(page_instance_id: &str) -> bool {
lxapp::notify_page_instance_by_id(page_instance_id, PageInstanceEvent::Visible).is_ok()
}
pub fn notify_page_instance_hidden(page_instance_id: &str, reason: &str) -> bool {
lxapp::notify_page_instance_by_id(
page_instance_id,
PageInstanceEvent::Hidden {
reason: parse_close_reason(reason),
},
)
.is_ok()
}
pub fn dispose_page_instance(page_instance_id: &str, reason: &str) -> bool {
lxapp::dispose_page_instance_by_id(page_instance_id, parse_close_reason(reason)).is_ok()
}
pub fn on_surface_closed(appid: &str, id: &str, reason: &str) -> bool {
if let Some(lxapp) = lxapp::try_get(appid) {
let _ = lxapp.forget_surface(id);
}
#[cfg(feature = "standard")]
{
lingxia_logic::notify_surface_closed(id, reason)
}
#[cfg(not(feature = "standard"))]
{
let _ = reason;
false
}
}
pub fn get_lxapp_info(appid: &str) -> self::bridge::LxAppInfo {
if let Some(lxapp) = lxapp::try_get(appid) {
let lxapp_info = lxapp.get_lxapp_info();
self::bridge::LxAppInfo {
app_name: lxapp_info.app_name,
version: lxapp_info.version,
release_type: lxapp_info.release_type,
cache_dir: lxapp.user_cache_dir.to_string_lossy().into_owned(),
icon: lxapp_info.icon,
}
} else {
self::bridge::LxAppInfo {
app_name: String::new(),
version: String::new(),
release_type: String::new(),
cache_dir: String::new(),
icon: String::new(),
}
}
}
pub fn get_navigation_bar_state(appid: &str, path: &str) -> self::bridge::NavigationBarState {
if let Some(lxapp) = lxapp::try_get(appid) {
let nav_state = lxapp.get_navbar_state(path);
let bg_color = parse_color_to_u32(&nav_state.navigationBarBackgroundColor, 0xFFFFFFFF);
self::bridge::NavigationBarState {
background_color: bg_color,
text_style: nav_state.navigationBarTextStyle,
title_text: nav_state.navigationBarTitleText,
show_navbar: nav_state.show_navbar,
show_back_button: nav_state.show_back_button,
show_home_button: nav_state.show_home_button,
}
} else {
self::bridge::NavigationBarState {
background_color: 0,
text_style: String::new(),
title_text: String::new(),
show_navbar: false,
show_back_button: false,
show_home_button: false,
}
}
}
pub fn get_page_orientation(appid: &str, path: &str) -> i32 {
let Some(lxapp_instance) = lxapp::try_get(appid) else {
return 0;
};
let orientation = lxapp_instance.get_page_orientation(path);
orientation_to_value(orientation)
}
fn orientation_to_value(orientation: OrientationConfig) -> i32 {
match (orientation.mode, orientation.rotation) {
(PageOrientation::Auto, _) => 0,
(PageOrientation::Portrait, 180) => 3,
(PageOrientation::Portrait, _) => 1,
(PageOrientation::Landscape, 180) => 4,
(PageOrientation::Landscape, _) => 2,
}
}
pub fn get_tab_bar(appid: &str) -> Option<self::bridge::TabBar> {
lxapp::try_get(appid).and_then(|lxapp| {
lxapp.get_tabbar().map(|tabbar| self::bridge::TabBar {
color: parse_color_to_u32(&tabbar.color, 0xFF666666),
selected_color: parse_color_to_u32(&tabbar.selectedColor, 0xFF1677FF),
background_color: parse_color_to_u32(&tabbar.backgroundColor, 0xFFFFFFFF),
border_style: parse_color_to_u32(&tabbar.borderStyle, 0xFFF0F0F0),
position: tabbar.position.to_i32(),
dimension: tabbar.dimension,
items_count: tabbar.list.len() as i32,
is_visible: tabbar.is_visible,
is_api_hidden: tabbar.api_hidden,
selected_index: tabbar.selected_index,
styled_mask: (!tabbar.color.is_empty() as u32)
| ((!tabbar.selectedColor.is_empty() as u32) << 1)
| ((!tabbar.backgroundColor.is_empty() as u32) << 2)
| ((!tabbar.borderStyle.is_empty() as u32) << 3),
})
})
}
pub fn get_tab_bar_item(appid: &str, index: i32) -> Option<self::bridge::TabBarItem> {
lxapp::try_get(appid)
.and_then(|lxapp| lxapp.get_tabbar())
.and_then(|tabbar| {
tabbar.get_item(index).map(|item| self::bridge::TabBarItem {
page_path: item.pagePath.clone(),
text: item.text.clone().unwrap_or_default(),
icon_path: item.iconPath.clone().unwrap_or_default(),
selected_icon_path: item.selectedIconPath.clone().unwrap_or_default(),
selected: item.selected,
badge: item.badge.clone().unwrap_or_default(),
has_red_dot: item.has_red_dot,
})
})
}
pub fn on_applink_received(url: &str) -> i32 {
lingxia_service::applink::handle(url)
}
pub fn on_pushlink_received(url: &str, trigger: self::bridge::PushTrigger) -> i32 {
let trigger_name = match trigger {
self::bridge::PushTrigger::Background => "Background",
self::bridge::PushTrigger::Tap => "Tap",
self::bridge::PushTrigger::Launch => "Launch",
};
log::info!(
"[Apple] Push Link received: {} (trigger: {})",
url,
trigger_name
);
lingxia_service::applink::handle(url)
}
pub fn on_push_token_received(token: &str) -> i32 {
crate::push::bind_push_token_for_ffi(token.to_string())
}
pub fn on_callback(id: u64, success: bool, data: &str) -> bool {
let result = if success {
Ok(data.to_string())
} else {
Err(data.parse::<u32>().unwrap_or(1000))
};
if invoke_callback(id, result) {
true
} else {
log::warn!("[Apple] Callback not found for id={}", id);
false
}
}
pub fn is_pull_down_refresh_enabled(appid: &str, path: &str) -> bool {
lxapp::is_pull_down_refresh_enabled(appid, path)
}
pub fn on_app_show(lxappid: &str) {
if let Some(lxapp) = lxapp::try_get(lxappid) {
let args = lxapp::AppServiceEventArgs {
source: lxapp::AppServiceEventSource::Host,
reason: lxapp::AppServiceEventReason::Foreground,
}
.to_json_string();
let _ = lxapp.appservice_notify(lxapp::AppServiceEvent::OnShow, Some(args));
}
}
pub fn on_app_hide(lxappid: &str) {
if let Some(lxapp) = lxapp::try_get(lxappid) {
let args = lxapp::AppServiceEventArgs {
source: lxapp::AppServiceEventSource::Host,
reason: lxapp::AppServiceEventReason::Background,
}
.to_json_string();
let _ = lxapp.appservice_notify(lxapp::AppServiceEvent::OnHide, Some(args));
}
}
pub fn on_user_capture_screen(lxappid: &str) {
if let Some(lxapp) = lxapp::try_get(lxappid) {
let args = lxapp::AppServiceEventArgs {
source: lxapp::AppServiceEventSource::Host,
reason: lxapp::AppServiceEventReason::Screenshot,
}
.to_json_string();
let _ = lxapp.appservice_notify(lxapp::AppServiceEvent::OnUserCaptureScreen, Some(args));
}
}
pub fn get_app_capabilities() -> u32 {
crate::capabilities::app_capabilities()
}
pub fn should_enable_webview_debugging() -> bool {
crate::should_enable_webview_debugging()
}
pub fn get_panels_config_json() -> Option<String> {
crate::browser::panels_config_json()
}
pub fn open_panel_lxapp(panel_id: &str, appid: &str, path: &str) {
crate::browser::open_panel_lxapp(panel_id, appid, path);
}
pub fn get_terminal_backend_status_json() -> String {
#[cfg(feature = "terminal-runtime")]
{
return crate::terminal::ghostty_status_json();
}
#[cfg(not(feature = "terminal-runtime"))]
{
r#"{"backend":"ghostty","available":false,"status":"terminal runtime disabled","sourceDir":null,"libDir":null}"#.to_string()
}
}
pub fn terminal_session_create(cols: u16, rows: u16) -> u64 {
#[cfg(feature = "terminal-runtime")]
{
return crate::terminal::terminal_create(cols, rows);
}
#[cfg(not(feature = "terminal-runtime"))]
{
let _ = (cols, rows);
0
}
}
pub fn terminal_session_write(id: u64, input: &str) -> bool {
#[cfg(feature = "terminal-runtime")]
{
return crate::terminal::terminal_write(id, input);
}
#[cfg(not(feature = "terminal-runtime"))]
{
let _ = (id, input);
false
}
}
pub fn terminal_session_read(id: u64) -> String {
#[cfg(feature = "terminal-runtime")]
{
return crate::terminal::terminal_read(id);
}
#[cfg(not(feature = "terminal-runtime"))]
{
let _ = id;
String::new()
}
}
pub fn terminal_session_snapshot(id: u64) -> String {
#[cfg(feature = "terminal-runtime")]
{
return crate::terminal::terminal_snapshot(id);
}
#[cfg(not(feature = "terminal-runtime"))]
{
let _ = id;
r#"{"cols":0,"rows":0,"lines":[],"cells":[],"cursor_row":0,"cursor_col":0,"cursor_visible":false,"application_cursor":false,"bracketed_paste":false,"alternate_screen":false,"scrollbar":null,"title":null,"generation":0,"exited":true}"#.to_string()
}
}
pub fn terminal_session_exited(id: u64) -> bool {
#[cfg(feature = "terminal-runtime")]
{
return crate::terminal::terminal_exited(id);
}
#[cfg(not(feature = "terminal-runtime"))]
{
let _ = id;
true
}
}
pub fn terminal_session_resize(id: u64, cols: u16, rows: u16) -> bool {
#[cfg(feature = "terminal-runtime")]
{
return crate::terminal::terminal_resize(id, cols, rows);
}
#[cfg(not(feature = "terminal-runtime"))]
{
let _ = (id, cols, rows);
false
}
}
pub fn terminal_session_scroll(
id: u64,
delta_rows: i32,
col: u16,
row: u16,
allow_application_input: bool,
) -> bool {
#[cfg(feature = "terminal-runtime")]
{
return crate::terminal::terminal_scroll(id, delta_rows, col, row, allow_application_input);
}
#[cfg(not(feature = "terminal-runtime"))]
{
let _ = (id, delta_rows, col, row, allow_application_input);
false
}
}
pub fn terminal_session_close(id: u64) {
#[cfg(feature = "terminal-runtime")]
{
crate::terminal::terminal_close(id);
}
#[cfg(not(feature = "terminal-runtime"))]
{
let _ = id;
}
}