All checks were successful
- Implemented automatic permission handling for microphone and camera access on Linux using webkit2gtk. - Updated tauri.conf.json to define the main application window with specific dimensions and properties.
227 lines
9.8 KiB
Rust
227 lines
9.8 KiB
Rust
//! Paarrot Desktop library for cross-platform builds including Android
|
|
|
|
#[cfg(mobile)]
|
|
mod mobile;
|
|
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
use tauri::{
|
|
Manager,
|
|
WebviewUrl,
|
|
menu::{Menu, MenuItem},
|
|
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
|
webview::WebviewWindowBuilder,
|
|
WindowEvent,
|
|
};
|
|
|
|
// Linux: Import for permission handling
|
|
#[cfg(target_os = "linux")]
|
|
use webkit2gtk::{PermissionRequestExt, WebViewExt};
|
|
#[cfg(target_os = "linux")]
|
|
use gtk::prelude::*;
|
|
|
|
/// Read image from clipboard on Linux using arboard with Wayland support
|
|
#[cfg(target_os = "linux")]
|
|
#[tauri::command]
|
|
fn read_clipboard_image() -> Result<Option<String>, String> {
|
|
use arboard::Clipboard;
|
|
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
|
|
|
let mut clipboard = Clipboard::new().map_err(|e| e.to_string())?;
|
|
|
|
match clipboard.get_image() {
|
|
Ok(img) => {
|
|
// Convert RGBA image data to PNG
|
|
let width = img.width as u32;
|
|
let height = img.height as u32;
|
|
|
|
let mut png_data = Vec::new();
|
|
{
|
|
let mut encoder = png::Encoder::new(&mut png_data, width, height);
|
|
encoder.set_color(png::ColorType::Rgba);
|
|
encoder.set_depth(png::BitDepth::Eight);
|
|
let mut writer = encoder.write_header().map_err(|e| e.to_string())?;
|
|
writer.write_image_data(&img.bytes).map_err(|e| e.to_string())?;
|
|
}
|
|
|
|
let base64_data = BASE64.encode(&png_data);
|
|
Ok(Some(format!("data:image/png;base64,{}", base64_data)))
|
|
}
|
|
Err(_) => Ok(None), // No image in clipboard
|
|
}
|
|
}
|
|
|
|
/// Stub for non-Linux platforms - returns None
|
|
#[cfg(not(target_os = "linux"))]
|
|
#[tauri::command]
|
|
fn read_clipboard_image() -> Result<Option<String>, String> {
|
|
Ok(None)
|
|
}
|
|
|
|
/// Open a URL in the default browser (bypasses ACL issues with localhost plugin)
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
#[tauri::command]
|
|
fn open_external_url(url: String) -> Result<(), String> {
|
|
open::that(&url).map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// Runs the Tauri application
|
|
pub fn run() {
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
{
|
|
let port = 44548;
|
|
tauri::Builder::default()
|
|
.plugin(tauri_plugin_localhost::Builder::new(port).build())
|
|
.plugin(tauri_plugin_opener::init())
|
|
.plugin(tauri_plugin_window_state::Builder::default().build())
|
|
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
|
// When a second instance tries to launch, focus the existing window
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
let _ = window.unminimize();
|
|
let _ = window.show();
|
|
let _ = window.set_focus();
|
|
}
|
|
}))
|
|
.plugin(tauri_plugin_notification::init())
|
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
|
.plugin(tauri_plugin_autostart::init(
|
|
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
|
|
Some(vec!["--minimized"]),
|
|
))
|
|
.setup(move |app| {
|
|
// In dev mode, use Vite dev server; in production, use localhost plugin
|
|
let url: tauri::Url = if cfg!(dev) {
|
|
"http://localhost:8080".parse().unwrap()
|
|
} else {
|
|
format!("http://localhost:{}", port).parse().unwrap()
|
|
};
|
|
|
|
// Create the main window manually with navigation handler for external links
|
|
WebviewWindowBuilder::new(app, "main", WebviewUrl::External(url))
|
|
.title("Paarrot")
|
|
.inner_size(1280.0, 905.0)
|
|
.center()
|
|
.resizable(true)
|
|
.disable_drag_drop_handler()
|
|
.on_navigation(|url| {
|
|
let url_str = url.as_str();
|
|
// Allow navigation to localhost (our app) and special protocols
|
|
if url_str.starts_with("http://localhost")
|
|
|| url_str.starts_with("https://localhost")
|
|
|| url_str.starts_with("tauri://")
|
|
|| url_str.starts_with("blob:")
|
|
|| url_str.starts_with("data:")
|
|
{
|
|
return true;
|
|
}
|
|
// Block external URLs - open them in default browser
|
|
if url_str.starts_with("http://") || url_str.starts_with("https://") {
|
|
let _ = tauri_plugin_opener::open_url(url_str, None::<&str>);
|
|
return false;
|
|
}
|
|
true
|
|
})
|
|
.build()?;
|
|
|
|
// Linux: Set up permission handler to auto-allow microphone/camera access
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
if let Some(webview_window) = app.get_webview_window("main") {
|
|
let _ = webview_window.with_webview(|webview| {
|
|
use webkit2gtk::UserMediaPermissionRequestExt;
|
|
|
|
let wv = webview.inner();
|
|
wv.connect_permission_request(|_webview, permission_request| {
|
|
// Check if this is a user media (microphone/camera) permission request
|
|
if let Some(user_media_request) = permission_request.downcast_ref::<webkit2gtk::UserMediaPermissionRequest>() {
|
|
// Log what's being requested
|
|
let is_audio = user_media_request.is_for_audio_device();
|
|
let is_video = user_media_request.is_for_video_device();
|
|
eprintln!("Paarrot: Media permission request - audio: {}, video: {}", is_audio, is_video);
|
|
|
|
// Allow the request
|
|
permission_request.allow();
|
|
return true;
|
|
}
|
|
|
|
// For other permission types, allow by default
|
|
permission_request.allow();
|
|
true
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create system tray
|
|
let show_item = MenuItem::with_id(app, "show", "Show Paarrot", true, None::<&str>)?;
|
|
let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
|
let menu = Menu::with_items(app, &[&show_item, &quit_item])?;
|
|
|
|
let _tray = TrayIconBuilder::new()
|
|
.icon(app.default_window_icon().unwrap().clone())
|
|
.menu(&menu)
|
|
.show_menu_on_left_click(false)
|
|
.on_menu_event(|app, event| match event.id.as_ref() {
|
|
"show" => {
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
let _ = window.unminimize();
|
|
let _ = window.show();
|
|
let _ = window.set_focus();
|
|
}
|
|
}
|
|
"quit" => {
|
|
app.exit(0);
|
|
}
|
|
_ => {}
|
|
})
|
|
.on_tray_icon_event(|tray, event| {
|
|
if let TrayIconEvent::Click {
|
|
button: MouseButton::Left,
|
|
button_state: MouseButtonState::Up,
|
|
..
|
|
} = event
|
|
{
|
|
let app = tray.app_handle();
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
let _ = window.unminimize();
|
|
let _ = window.show();
|
|
let _ = window.set_focus();
|
|
}
|
|
}
|
|
})
|
|
.build(app)?;
|
|
|
|
Ok(())
|
|
})
|
|
.on_window_event(|window, event| {
|
|
// Minimize to tray on close instead of quitting
|
|
if let WindowEvent::CloseRequested { api, .. } = event {
|
|
window.hide().unwrap();
|
|
api.prevent_close();
|
|
}
|
|
})
|
|
.invoke_handler(tauri::generate_handler![read_clipboard_image, open_external_url])
|
|
.run(tauri::generate_context!())
|
|
.expect("error while building tauri application");
|
|
}
|
|
|
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
{
|
|
use tauri::{WebviewUrl, webview::WebviewWindowBuilder};
|
|
|
|
tauri::Builder::default()
|
|
.plugin(tauri_plugin_opener::init())
|
|
.plugin(tauri_plugin_notification::init())
|
|
.plugin(tauri_plugin_deep_link::init())
|
|
.setup(|app| {
|
|
// Create the main window for mobile
|
|
// Mobile uses the bundled assets directly, not localhost
|
|
WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
|
.build()?;
|
|
Ok(())
|
|
})
|
|
.invoke_handler(tauri::generate_handler![read_clipboard_image])
|
|
.run(tauri::generate_context!())
|
|
.expect("error while building tauri application");
|
|
}
|
|
}
|