* -------------------------------------------------------------------------
* This file is part of the MindStudio project.
* Copyright (c) 2025 Huawei Technologies Co.,Ltd.
*
* MindStudio is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*/
mod cleanup;
#[cfg(windows)]
pub mod webview2err;
use std::{fs::read, path::PathBuf, sync::Arc, process::Command};
use std::path::Path;
#[cfg(target_os = "macos")]
use wry::application::menu::{MenuBar, MenuItem};
pub use wry::webview::webview_version;
use wry::{
application::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop, EventLoopProxy},
window::{Window, WindowBuilder},
},
http::{header::CONTENT_TYPE, Response},
webview::{FileDropEvent, WebView, WebViewBuilder},
};
const MIMETYPE_HTML: &str = "text/html";
fn create_webview(
window: Window,
cache_path: Arc<PathBuf>,
resource_path: Arc<PathBuf>,
port: u16,
proxy: Arc<EventLoopProxy<PathBuf>>,
) -> wry::Result<WebView> {
WebViewBuilder::new(window)?
.with_custom_protocol("wry".into(), move |request| {
let path = request.uri().path();
let content = match read(resource_path.join(&path[1..]).as_path()) {
Ok(a) => a.into(),
Err(e) => return Err(wry::Error::Io(e)),
};
let mimetype = extract_mimetype(path);
Response::builder()
.header(CONTENT_TYPE, mimetype)
.body(content)
.map_err(Into::into)
})
.with_url(format!("wry://localhost/resources/profiler/frontend/index.html?port={}", port).as_str())?
.with_file_drop_handler(move |_, ev| {
match ev {
FileDropEvent::Dropped(paths) => {
if let Err(e) = proxy.send_event(paths[0].to_owned()) {
eprintln!("app closed unexpectedly: {:#?}", e);
}
}
_ => {}
}
true
})
.with_ipc_handler(move |_, front_end_msg| {
println!("Platform received message from frontend: {}", front_end_msg);
if front_end_msg == "showLogInExplorer" {
open_in_explorer(cache_path.as_ref().to_str().expect("Cache path is not valid UTF-8"));
return;
}
if front_end_msg.starts_with("openProjectInExplorer") {
handle_open_project_msg(&front_end_msg);
return;
}
if front_end_msg.starts_with("openUrl") {
handle_open_url_msg(&front_end_msg);
return;
}
})
.build()
}
fn handle_open_url_msg(front_end_msg: &str) {
if let Some(index) = front_end_msg.find('|') {
if index + 1 >= front_end_msg.len() {
eprintln!("Front end message: open url has a syntax error");
return;
}
let url = &front_end_msg[index + 1..];
open_in_explorer(url);
}
}
fn handle_open_project_msg(front_end_msg: &str) {
if let Some(index) = front_end_msg.find('|') {
if index + 1 >= front_end_msg.len() {
eprintln!("Front end message: open project in explorer has a syntax error");
return;
}
let path = &front_end_msg[index + 1..];
let mut path = Path::new(path);
if !path.exists() {
eprintln!("Front end message: open project in explorer path does not exist");
return;
}
if path.is_file() {
path = path.parent().expect("Failed to get parent directory");
}
let path = path.to_str().expect("Path is not valid UTF-8");
open_in_explorer(&path);
}
}
fn open_in_explorer(path: &str) {
#[cfg(target_os = "windows")]
let mut command = Command::new("explorer");
#[cfg(target_os = "linux")]
let mut command = Command::new("xdg-open");
#[cfg(target_os = "macos")]
let mut command = Command::new("open");
let result = match command
.arg(path)
.spawn()
{
Ok(_) => format!("Opened {} successfully", path),
Err(e) => format!("Failed to open {}, error message: {}", path, e),
};
println!("{}", result);
}
fn handle_user_event(webview: &WebView, path: PathBuf) {
if let Err(e) =
webview.evaluate_script(&format!("window.handleDrop({:#?})", path))
{
eprintln!("drop-file ipc failed: {:#?}", e);
}
}
pub fn run_event_loop(event_loop: EventLoop<PathBuf>, webview: WebView) {
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested, ..
}
| Event::WindowEvent { event: WindowEvent::Destroyed, .. } => {
cleanup::handle_close_requested();
*control_flow = ControlFlow::Exit;
}
Event::UserEvent(path) => handle_user_event(&webview, path),
_ => (),
}
});
}
#[cfg(windows)]
fn set_windows_icon(window: &Window, root_path: &PathBuf) {
use wry::application::{platform::windows::IconExtWindows, window::Icon};
window.set_window_icon(
Icon::from_path(
root_path
.to_path_buf()
.join("resources/images/icons/mindstudio.ico"),
None,
)
.ok(),
);
}
#[cfg(target_os = "macos")]
fn macos_menu() -> MenuBar {
let mut menu = MenuBar::new();
let mut window_menu = MenuBar::new();
window_menu.add_native_item(MenuItem::Minimize);
window_menu.add_native_item(MenuItem::Hide);
window_menu.add_native_item(MenuItem::HideOthers);
window_menu.add_native_item(MenuItem::Separator);
window_menu.add_native_item(MenuItem::Services);
window_menu.add_native_item(MenuItem::Separator);
window_menu.add_native_item(MenuItem::CloseWindow);
window_menu.add_native_item(MenuItem::Quit);
menu.add_submenu("Window", true, window_menu);
let mut edit_menu = MenuBar::new();
edit_menu.add_native_item(MenuItem::Cut);
edit_menu.add_native_item(MenuItem::Copy);
edit_menu.add_native_item(MenuItem::Paste);
edit_menu.add_native_item(MenuItem::SelectAll);
menu.add_submenu("Edit", true, edit_menu);
menu
}
pub fn run_script(
root_path: &PathBuf,
cache_path: &PathBuf,
port: u16,
) -> wry::Result<(EventLoop<PathBuf>, WebView)> {
let event_loop = EventLoop::with_user_event();
let proxy = Arc::new(event_loop.create_proxy());
let mut window_builder: WindowBuilder = WindowBuilder::new()
.with_title("MindStudio Insight")
.with_maximized(true);
#[cfg(target_os = "macos")]
{
let menu = macos_menu();
window_builder = window_builder.with_menu(menu);
}
let window = window_builder
.build(&event_loop)
.expect("Error occurred when create App window");
let resource_path = Arc::new(root_path.to_path_buf());
let log_path = Arc::new(cache_path.to_path_buf());
#[cfg(windows)]
set_windows_icon(&window, root_path);
let webview = create_webview(window, log_path, resource_path, port, proxy)?;
Ok((event_loop, webview))
}
fn extract_mimetype(path: &str) -> &str {
let mut mimetype = MIMETYPE_HTML;
if let Some((_, ext)) = path.rsplit_once('.') {
mimetype = match ext {
"html" => "text/html",
"js" => "text/javascript",
"css" => "text/css",
"svg" => "image/svg+xml",
"png" => "image/png",
"wasm" => "application/wasm",
_ => MIMETYPE_HTML,
}
}
mimetype
}