mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-22 05:34:04 +02:00
implemented new image saving
This commit is contained in:
parent
720f10bdbe
commit
08f79ece60
9 changed files with 198 additions and 128 deletions
|
@ -3,19 +3,40 @@ use base64::engine::general_purpose::STANDARD;
|
|||
use base64::Engine;
|
||||
use image::io::Reader as ImageReader;
|
||||
use image::{DynamicImage, ImageBuffer, ImageFormat, Rgba};
|
||||
use lazy_static::lazy_static;
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rdev::{simulate, EventType, Key};
|
||||
use regex::Regex;
|
||||
use reqwest::Client;
|
||||
use sha2::{Sha256, Digest};
|
||||
use sqlx::SqlitePool;
|
||||
use std::fs;
|
||||
use std::io::Cursor;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use tauri::Manager;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tokio::runtime::Runtime;
|
||||
use url::Url;
|
||||
|
||||
lazy_static! {
|
||||
static ref APP_DATA_DIR: Mutex<Option<std::path::PathBuf>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
pub fn set_app_data_dir(path: std::path::PathBuf) {
|
||||
let mut dir = APP_DATA_DIR.lock().unwrap();
|
||||
*dir = Some(path);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn read_image(filename: String) -> Result<Vec<u8>, String> {
|
||||
let app_data_dir = APP_DATA_DIR.lock().unwrap();
|
||||
let app_data_dir = app_data_dir.as_ref().expect("App data directory not set");
|
||||
let image_path = app_data_dir.join("images").join(filename);
|
||||
fs::read(image_path).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn simulate_paste() {
|
||||
let mut events = vec![
|
||||
|
@ -33,10 +54,18 @@ pub fn simulate_paste() {
|
|||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_image_path(app_handle: tauri::AppHandle, filename: String) -> String {
|
||||
let app_data_dir = app_handle.path().app_data_dir().expect("Failed to get app data directory");
|
||||
let image_path = app_data_dir.join("images").join(filename);
|
||||
image_path.to_str().unwrap_or("").to_string()
|
||||
}
|
||||
|
||||
pub fn setup(app_handle: tauri::AppHandle) {
|
||||
let is_processing = std::sync::Arc::new(std::sync::Mutex::new(false));
|
||||
|
||||
std::thread::spawn({
|
||||
let app_handle = app_handle.clone();
|
||||
let is_processing = std::sync::Arc::clone(&is_processing);
|
||||
move || {
|
||||
let mut clipboard = Clipboard::new().unwrap();
|
||||
|
@ -53,7 +82,7 @@ pub fn setup(app_handle: tauri::AppHandle) {
|
|||
if content != last_text {
|
||||
last_text = content.clone();
|
||||
rt.block_on(async {
|
||||
insert_content_if_not_exists(&pool, "text", content).await;
|
||||
insert_content_if_not_exists(&app_handle, &pool, "text", content).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +92,7 @@ pub fn setup(app_handle: tauri::AppHandle) {
|
|||
Ok(png_image) => {
|
||||
let base64_image = STANDARD.encode(&png_image);
|
||||
rt.block_on(async {
|
||||
insert_content_if_not_exists(&pool, "image", base64_image).await;
|
||||
insert_content_if_not_exists(&app_handle, &pool, "image", base64_image).await;
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -97,7 +126,7 @@ fn process_clipboard_image(
|
|||
Ok(png_bytes)
|
||||
}
|
||||
|
||||
async fn insert_content_if_not_exists(pool: &SqlitePool, content_type: &str, content: String) {
|
||||
async fn insert_content_if_not_exists(app_handle: &AppHandle, pool: &SqlitePool, content_type: &str, content: String) {
|
||||
let last_content: Option<String> = sqlx::query_scalar(
|
||||
"SELECT content FROM history WHERE content_type = ? ORDER BY timestamp DESC LIMIT 1",
|
||||
)
|
||||
|
@ -106,6 +135,18 @@ async fn insert_content_if_not_exists(pool: &SqlitePool, content_type: &str, con
|
|||
.await
|
||||
.unwrap_or(None);
|
||||
|
||||
let content = if content_type == "image" {
|
||||
match save_image(app_handle, &content).await {
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
println!("Failed to save image: {}", e);
|
||||
content
|
||||
}
|
||||
}
|
||||
} else {
|
||||
content
|
||||
};
|
||||
|
||||
if last_content.as_deref() != Some(&content) {
|
||||
let id: String = thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
|
@ -113,7 +154,7 @@ async fn insert_content_if_not_exists(pool: &SqlitePool, content_type: &str, con
|
|||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
let url_regex = Regex::new(r"^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$").unwrap();
|
||||
let url_regex = Regex::new(r"^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)$").unwrap();
|
||||
let favicon_base64 = if content_type == "text" && url_regex.is_match(&content) {
|
||||
match Url::parse(&content) {
|
||||
Ok(url) => match fetch_favicon_as_base64(url).await {
|
||||
|
@ -138,13 +179,32 @@ async fn insert_content_if_not_exists(pool: &SqlitePool, content_type: &str, con
|
|||
)
|
||||
.bind(id)
|
||||
.bind(content_type)
|
||||
.bind(content)
|
||||
.bind(&content)
|
||||
.bind(favicon_base64)
|
||||
.execute(pool)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_image(app_handle: &AppHandle, base64_image: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let image_data = STANDARD.decode(base64_image)?;
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&image_data);
|
||||
let hash = hasher.finalize();
|
||||
let filename = format!("{:x}.png", hash);
|
||||
|
||||
let app_data_dir = app_handle.path().app_data_dir().expect("Failed to get app data directory");
|
||||
let images_dir = app_data_dir.join("images");
|
||||
let path = images_dir.join(&filename);
|
||||
|
||||
if !path.exists() {
|
||||
fs::create_dir_all(&images_dir)?;
|
||||
fs::write(&path, &image_data)?;
|
||||
}
|
||||
|
||||
Ok(path.to_str().unwrap().to_string())
|
||||
}
|
||||
|
||||
async fn fetch_favicon_as_base64(url: Url) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
let client = Client::new();
|
||||
let favicon_url = format!("https://icon.horse/icon/{}", url.host_str().unwrap());
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use rdev::{listen, EventType, Key};
|
||||
use tauri::Manager;
|
||||
|
||||
use crate::center_window_on_current_monitor;
|
||||
|
||||
pub fn setup(app_handle: tauri::AppHandle) {
|
||||
std::thread::spawn(move || {
|
||||
let mut meta_pressed = false;
|
||||
|
@ -18,6 +20,7 @@ pub fn setup(app_handle: tauri::AppHandle) {
|
|||
let window = app_handle.get_webview_window("main").unwrap();
|
||||
window.show().unwrap();
|
||||
window.set_focus().unwrap();
|
||||
center_window_on_current_monitor(&window);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
@ -12,18 +12,32 @@ use tauri::Manager;
|
|||
use tauri::PhysicalPosition;
|
||||
use tauri_plugin_autostart::MacosLauncher;
|
||||
|
||||
fn center_window_on_current_monitor(window: &tauri::WebviewWindow) {
|
||||
if let Some(monitor) = window.current_monitor().unwrap() {
|
||||
pub fn center_window_on_current_monitor(window: &tauri::WebviewWindow) {
|
||||
if let Some(monitor) = window.available_monitors().unwrap().iter().find(|m| {
|
||||
let primary_monitor = window
|
||||
.primary_monitor()
|
||||
.unwrap()
|
||||
.expect("Failed to get primary monitor");
|
||||
let mouse_position = primary_monitor.position();
|
||||
let monitor_position = m.position();
|
||||
let monitor_size = m.size();
|
||||
mouse_position.x >= monitor_position.x
|
||||
&& mouse_position.x < monitor_position.x + monitor_size.width as i32
|
||||
&& mouse_position.y >= monitor_position.y
|
||||
&& mouse_position.y < monitor_position.y + monitor_size.height as i32
|
||||
}) {
|
||||
let monitor_size = monitor.size();
|
||||
let window_size = window.outer_size().unwrap();
|
||||
|
||||
let x = (monitor_size.width as i32 - window_size.width as i32) / 2;
|
||||
let y = (monitor_size.height as i32 - window_size.height as i32) / 2;
|
||||
|
||||
window.set_position(PhysicalPosition::new(
|
||||
monitor.position().x + x,
|
||||
monitor.position().y + y,
|
||||
)).unwrap();
|
||||
window
|
||||
.set_position(PhysicalPosition::new(
|
||||
monitor.position().x + x,
|
||||
monitor.position().y + y,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +45,6 @@ fn main() {
|
|||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||
.plugin(tauri_plugin_sql::Builder::default().build())
|
||||
.plugin(tauri_plugin_autostart::init(
|
||||
MacosLauncher::LaunchAgent,
|
||||
|
@ -57,6 +70,9 @@ fn main() {
|
|||
window.close_devtools();
|
||||
}
|
||||
|
||||
let app_data_dir = app.path().app_data_dir().expect("Failed to get app data directory");
|
||||
clipboard::set_app_data_dir(app_data_dir);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_window_event(|app, event| match event {
|
||||
|
@ -68,7 +84,11 @@ fn main() {
|
|||
}
|
||||
_ => {}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![clipboard::simulate_paste])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
clipboard::simulate_paste,
|
||||
clipboard::get_image_path,
|
||||
clipboard::read_image
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue