use tauri::{Manager, Runtime};
use victauri_core::WindowState;
pub trait WebviewBridge: Send + Sync {
fn eval_webview(&self, label: Option<&str>, script: &str) -> Result<(), String>;
fn get_window_states(&self, label: Option<&str>) -> Vec<WindowState>;
fn list_window_labels(&self) -> Vec<String>;
fn get_native_handle(&self, label: Option<&str>) -> Result<isize, String>;
fn manage_window(&self, label: Option<&str>, action: &str) -> Result<String, String>;
fn resize_window(&self, label: Option<&str>, width: u32, height: u32) -> Result<(), String>;
fn move_window(&self, label: Option<&str>, x: i32, y: i32) -> Result<(), String>;
fn set_window_title(&self, label: Option<&str>, title: &str) -> Result<(), String>;
}
fn find_window<'a, R: Runtime>(
windows: &'a std::collections::HashMap<String, tauri::WebviewWindow<R>>,
label: Option<&str>,
) -> Result<&'a tauri::WebviewWindow<R>, String> {
match label {
Some(l) => windows
.get(l)
.ok_or_else(|| format!("window not found: {l}")),
None => windows
.get("main")
.or_else(|| windows.values().find(|w| w.is_visible().unwrap_or(false)))
.or_else(|| windows.values().next())
.ok_or_else(|| "no window available".to_string()),
}
}
impl<R: Runtime> WebviewBridge for tauri::AppHandle<R> {
fn eval_webview(&self, label: Option<&str>, script: &str) -> Result<(), String> {
let windows = self.webview_windows();
let webview = find_window(&windows, label)?;
webview.eval(script).map_err(|e| e.to_string())
}
fn get_window_states(&self, label: Option<&str>) -> Vec<WindowState> {
let windows = self.webview_windows();
let mut states = Vec::new();
for (win_label, window) in &windows {
if let Some(filter) = label
&& win_label != filter
{
continue;
}
let pos = window.outer_position().unwrap_or_default();
let size = window.inner_size().unwrap_or_default();
states.push(WindowState {
label: win_label.clone(),
title: window.title().unwrap_or_default(),
url: window.url().map(|u| u.to_string()).unwrap_or_default(),
visible: window.is_visible().unwrap_or(false),
focused: window.is_focused().unwrap_or(false),
maximized: window.is_maximized().unwrap_or(false),
minimized: window.is_minimized().unwrap_or(false),
fullscreen: window.is_fullscreen().unwrap_or(false),
position: (pos.x, pos.y),
size: (size.width, size.height),
});
}
states
}
fn list_window_labels(&self) -> Vec<String> {
self.webview_windows().keys().cloned().collect()
}
fn get_native_handle(&self, label: Option<&str>) -> Result<isize, String> {
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
let windows = self.webview_windows();
let _webview = find_window(&windows, label)?;
let handle = _webview.window_handle().map_err(|e| e.to_string())?;
match handle.as_raw() {
#[cfg(windows)]
RawWindowHandle::Win32(h) => Ok(h.hwnd.get()),
#[cfg(target_os = "macos")]
RawWindowHandle::AppKit(h) => {
macos_window_number(h.ns_view.as_ptr())
}
#[cfg(target_os = "linux")]
RawWindowHandle::Xlib(h) => Ok(h.window as isize),
#[cfg(target_os = "linux")]
RawWindowHandle::Xcb(h) => Ok(h.window.get() as isize),
_ => Err("unsupported window handle type on this platform".to_string()),
}
}
fn manage_window(&self, label: Option<&str>, action: &str) -> Result<String, String> {
let windows = self.webview_windows();
let window = find_window(&windows, label)?;
match action {
"minimize" => window.minimize().map_err(|e| e.to_string())?,
"unminimize" => window.unminimize().map_err(|e| e.to_string())?,
"maximize" => window.maximize().map_err(|e| e.to_string())?,
"unmaximize" => window.unmaximize().map_err(|e| e.to_string())?,
"close" => window.close().map_err(|e| e.to_string())?,
"focus" => window.set_focus().map_err(|e| e.to_string())?,
"show" => window.show().map_err(|e| e.to_string())?,
"hide" => window.hide().map_err(|e| e.to_string())?,
"fullscreen" => window.set_fullscreen(true).map_err(|e| e.to_string())?,
"unfullscreen" => window.set_fullscreen(false).map_err(|e| e.to_string())?,
"always_on_top" => window.set_always_on_top(true).map_err(|e| e.to_string())?,
"not_always_on_top" => window.set_always_on_top(false).map_err(|e| e.to_string())?,
_ => return Err(format!("unknown action: {action}")),
}
Ok(format!("{action} executed"))
}
fn resize_window(&self, label: Option<&str>, width: u32, height: u32) -> Result<(), String> {
let windows = self.webview_windows();
let window = find_window(&windows, label)?;
window
.set_size(tauri::LogicalSize::new(width, height))
.map_err(|e| e.to_string())
}
fn move_window(&self, label: Option<&str>, x: i32, y: i32) -> Result<(), String> {
let windows = self.webview_windows();
let window = find_window(&windows, label)?;
window
.set_position(tauri::LogicalPosition::new(x, y))
.map_err(|e| e.to_string())
}
fn set_window_title(&self, label: Option<&str>, title: &str) -> Result<(), String> {
let windows = self.webview_windows();
let window = find_window(&windows, label)?;
window.set_title(title).map_err(|e| e.to_string())
}
}
#[cfg(target_os = "macos")]
#[allow(unsafe_code)]
fn macos_window_number(ns_view: *mut std::ffi::c_void) -> Result<isize, String> {
unsafe extern "C" {
fn objc_msgSend(obj: *mut std::ffi::c_void, sel: *mut std::ffi::c_void) -> isize;
fn sel_registerName(name: *const std::ffi::c_char) -> *mut std::ffi::c_void;
}
if ns_view.is_null() {
return Err("null NSView handle".to_string());
}
unsafe {
let sel_window = sel_registerName(c"window".as_ptr());
let ns_window = objc_msgSend(ns_view, sel_window);
if ns_window == 0 {
return Err("NSView has no parent NSWindow".to_string());
}
let sel_window_number = sel_registerName(c"windowNumber".as_ptr());
let ns_window_ptr = ns_window as *mut std::ffi::c_void;
let window_number = objc_msgSend(ns_window_ptr, sel_window_number);
if window_number <= 0 {
return Err(format!("invalid CGWindowID: {window_number}"));
}
Ok(window_number)
}
}