mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-21 21:24:05 +02:00
added new icons
This commit is contained in:
parent
14fb5c37a9
commit
51d2243e73
13 changed files with 195 additions and 185 deletions
|
@ -1,22 +1,17 @@
|
|||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use tauri::{AppHandle, Manager, Runtime, Emitter, Listener};
|
||||
use base64::Engine;
|
||||
use image::ImageFormat;
|
||||
use lazy_static::lazy_static;
|
||||
use rand::Rng;
|
||||
use rdev::{simulate, EventType, Key};
|
||||
use regex::Regex;
|
||||
use sha2::{Digest, Sha256};
|
||||
use sqlx::SqlitePool;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{fs, sync::Mutex, thread, time::Duration};
|
||||
use tauri::{AppHandle, Emitter, Listener, Manager, Runtime};
|
||||
use tauri_plugin_clipboard::Clipboard;
|
||||
use tokio::runtime::Runtime as TokioRuntime;
|
||||
use regex::Regex;
|
||||
use sqlx::SqlitePool;
|
||||
use std::{
|
||||
fs,
|
||||
sync::Mutex,
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
use rand::Rng;
|
||||
use sha2::{Sha256, Digest};
|
||||
use rdev::{simulate, Key, EventType};
|
||||
use lazy_static::lazy_static;
|
||||
use image::ImageFormat;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
lazy_static! {
|
||||
static ref APP_DATA_DIR: Mutex<Option<std::path::PathBuf>> = Mutex::new(None);
|
||||
|
@ -37,17 +32,30 @@ pub fn read_image(filename: String) -> Result<Vec<u8>, String> {
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn write_and_paste<R: Runtime>(app_handle: tauri::AppHandle<R>, content: String, content_type: String) -> Result<(), String> {
|
||||
pub async fn write_and_paste<R: Runtime>(
|
||||
app_handle: tauri::AppHandle<R>,
|
||||
content: String,
|
||||
content_type: String,
|
||||
) -> Result<(), String> {
|
||||
let clipboard = app_handle.state::<Clipboard>();
|
||||
|
||||
match content_type.as_str() {
|
||||
"text" => clipboard.write_text(content).map_err(|e| e.to_string())?,
|
||||
"image" => {
|
||||
clipboard.write_image_base64(content).map_err(|e| e.to_string())?;
|
||||
},
|
||||
clipboard
|
||||
.write_image_base64(content)
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
"files" => {
|
||||
clipboard.write_files_uris(content.split(", ").map(|file| file.to_string()).collect::<Vec<String>>()).map_err(|e| e.to_string())?;
|
||||
},
|
||||
clipboard
|
||||
.write_files_uris(
|
||||
content
|
||||
.split(", ")
|
||||
.map(|file| file.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
_ => return Err("Unsupported content type".to_string()),
|
||||
}
|
||||
|
||||
|
@ -81,7 +89,10 @@ 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 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()
|
||||
}
|
||||
|
@ -90,61 +101,94 @@ pub fn setup<R: Runtime>(app: &AppHandle<R>) {
|
|||
let app = app.clone();
|
||||
let runtime = TokioRuntime::new().expect("Failed to create Tokio runtime");
|
||||
|
||||
app.clone().listen("plugin:clipboard://clipboard-monitor/update", move |_event| {
|
||||
let app = app.clone();
|
||||
runtime.block_on(async move {
|
||||
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
|
||||
println!("Ignoring programmatic paste");
|
||||
return;
|
||||
}
|
||||
app.clone().listen(
|
||||
"plugin:clipboard://clipboard-monitor/update",
|
||||
move |_event| {
|
||||
let app = app.clone();
|
||||
runtime.block_on(async move {
|
||||
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
|
||||
println!("Ignoring programmatic paste");
|
||||
return;
|
||||
}
|
||||
|
||||
let clipboard = app.state::<Clipboard>();
|
||||
let available_types = clipboard.available_types().unwrap();
|
||||
|
||||
println!("Clipboard update detected");
|
||||
let clipboard = app.state::<Clipboard>();
|
||||
let available_types = clipboard.available_types().unwrap();
|
||||
|
||||
match get_pool(&app).await {
|
||||
Ok(pool) => {
|
||||
if available_types.image {
|
||||
println!("Handling image change");
|
||||
if let Ok(image_data) = clipboard.read_image_base64() {
|
||||
let base64_image = STANDARD.encode(&image_data);
|
||||
insert_content_if_not_exists(app.clone(), pool.clone(), "image", base64_image).await;
|
||||
println!("Clipboard update detected");
|
||||
|
||||
match get_pool(&app).await {
|
||||
Ok(pool) => {
|
||||
if available_types.image {
|
||||
println!("Handling image change");
|
||||
if let Ok(image_data) = clipboard.read_image_base64() {
|
||||
let base64_image = STANDARD.encode(&image_data);
|
||||
insert_content_if_not_exists(
|
||||
app.clone(),
|
||||
pool.clone(),
|
||||
"image",
|
||||
base64_image,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let _ = app.emit("plugin:clipboard://image-changed", ());
|
||||
} else if available_types.files {
|
||||
println!("Handling files change");
|
||||
if let Ok(files) = clipboard.read_files() {
|
||||
let files_str = files.join(", ");
|
||||
insert_content_if_not_exists(
|
||||
app.clone(),
|
||||
pool.clone(),
|
||||
"files",
|
||||
files_str,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let _ = app.emit("plugin:clipboard://files-changed", ());
|
||||
} else if available_types.text {
|
||||
println!("Handling text change");
|
||||
if let Ok(text) = clipboard.read_text() {
|
||||
insert_content_if_not_exists(
|
||||
app.clone(),
|
||||
pool.clone(),
|
||||
"text",
|
||||
text,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let _ = app.emit("plugin:clipboard://text-changed", ());
|
||||
} else {
|
||||
println!("Unknown clipboard content type");
|
||||
}
|
||||
let _ = app.emit("plugin:clipboard://image-changed", ());
|
||||
} else if available_types.files {
|
||||
println!("Handling files change");
|
||||
if let Ok(files) = clipboard.read_files() {
|
||||
let files_str = files.join(", ");
|
||||
insert_content_if_not_exists(app.clone(), pool.clone(), "files", files_str).await;
|
||||
}
|
||||
let _ = app.emit("plugin:clipboard://files-changed", ());
|
||||
} else if available_types.text {
|
||||
println!("Handling text change");
|
||||
if let Ok(text) = clipboard.read_text() {
|
||||
insert_content_if_not_exists(app.clone(), pool.clone(), "text", text).await;
|
||||
}
|
||||
let _ = app.emit("plugin:clipboard://text-changed", ());
|
||||
} else {
|
||||
println!("Unknown clipboard content type");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to get database pool: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to get database pool: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async fn get_pool<R: Runtime>(app_handle: &AppHandle<R>) -> Result<SqlitePool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let app_data_dir = app_handle.path().app_data_dir().expect("Failed to get app data directory");
|
||||
async fn get_pool<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
) -> Result<SqlitePool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let app_data_dir = app_handle
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.expect("Failed to get app data directory");
|
||||
let db_path = app_data_dir.join("data.db");
|
||||
let database_url = format!("sqlite:{}", db_path.to_str().unwrap());
|
||||
SqlitePool::connect(&database_url).await.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
|
||||
SqlitePool::connect(&database_url)
|
||||
.await
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
|
||||
}
|
||||
|
||||
async fn insert_content_if_not_exists<R: Runtime>(app_handle: AppHandle<R>, pool: SqlitePool, content_type: &str, content: String) {
|
||||
async fn insert_content_if_not_exists<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
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",
|
||||
)
|
||||
|
@ -172,21 +216,25 @@ async fn insert_content_if_not_exists<R: Runtime>(app_handle: AppHandle<R>, pool
|
|||
.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 favicon_base64 = if content_type == "text" && url_regex.is_match(&content) {
|
||||
match url::Url::parse(&content) {
|
||||
Ok(url) => match fetch_favicon_as_base64(url).await {
|
||||
Ok(Some(favicon)) => Some(favicon),
|
||||
Ok(None) => None,
|
||||
let favicon_base64 = if content_type == "text" {
|
||||
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();
|
||||
if url_regex.is_match(&content) {
|
||||
match url::Url::parse(&content) {
|
||||
Ok(url) => match fetch_favicon_as_base64(url).await {
|
||||
Ok(Some(favicon)) => Some(favicon),
|
||||
Ok(None) => None,
|
||||
Err(e) => {
|
||||
println!("Failed to fetch favicon: {}", e);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Failed to fetch favicon: {}", e);
|
||||
println!("Failed to parse URL: {}", e);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Failed to parse URL: {}", e);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
@ -204,26 +252,34 @@ async fn insert_content_if_not_exists<R: Runtime>(app_handle: AppHandle<R>, pool
|
|||
}
|
||||
}
|
||||
|
||||
async fn save_image<R: Runtime>(app_handle: &AppHandle<R>, base64_image: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
async fn save_image<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
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 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::Url) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
async fn fetch_favicon_as_base64(
|
||||
url: url::Url,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
let favicon_url = format!("https://favicone.com/{}", url.host_str().unwrap());
|
||||
let response = client.get(&favicon_url).send().await?;
|
||||
|
@ -242,7 +298,11 @@ async fn fetch_favicon_as_base64(url: url::Url) -> Result<Option<String>, Box<dy
|
|||
#[tauri::command]
|
||||
pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> {
|
||||
let clipboard = app_handle.state::<Clipboard>();
|
||||
clipboard.start_monitor(app_handle.clone()).map_err(|e| e.to_string())?;
|
||||
app_handle.emit("plugin:clipboard://clipboard-monitor/status", true).map_err(|e| e.to_string())?;
|
||||
clipboard
|
||||
.start_monitor(app_handle.clone())
|
||||
.map_err(|e| e.to_string())?;
|
||||
app_handle
|
||||
.emit("plugin:clipboard://clipboard-monitor/status", true)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue