From c91f67944d4411e98593728e72fdb202ad845fb7 Mon Sep 17 00:00:00 2001 From: cast1e Date: Mon, 14 Oct 2024 11:17:20 +0800 Subject: [PATCH] system tray --- wallitor-gui/src-tauri/Cargo.toml | 2 +- wallitor-gui/src-tauri/src/lib.rs | 240 ++++++++++++--------- wallitor-gui/src-tauri/src/setup.rs | 49 ++++- wallitor-gui/src/assets/svgs/delete.svg | 1 + wallitor-gui/src/components/AddItem.vue | 46 +++- wallitor-gui/src/components/ApplyBar.vue | 2 +- wallitor-gui/src/components/CDialog.vue | 2 +- wallitor-gui/src/components/CRMenu.vue | 85 ++++++++ wallitor-gui/src/components/CRMenuCell.vue | 37 ++++ wallitor-gui/src/components/ItemCard.vue | 6 +- wallitor-gui/src/components/TitleBar.vue | 3 +- wallitor-gui/src/store/index.ts | 98 ++++++--- wallitor-gui/src/store/vuex.d.ts | 13 +- wallitor-gui/src/views/HomeView.vue | 63 +++++- wallitor-gui/tsconfig.json | 7 +- 15 files changed, 489 insertions(+), 165 deletions(-) create mode 100644 wallitor-gui/src/assets/svgs/delete.svg create mode 100644 wallitor-gui/src/components/CRMenu.vue create mode 100644 wallitor-gui/src/components/CRMenuCell.vue diff --git a/wallitor-gui/src-tauri/Cargo.toml b/wallitor-gui/src-tauri/Cargo.toml index dae1e71..da1b191 100644 --- a/wallitor-gui/src-tauri/Cargo.toml +++ b/wallitor-gui/src-tauri/Cargo.toml @@ -16,7 +16,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2.0.0-rc", features = [] } [dependencies] -tauri = { version = "2.0.0-rc", features = ["unstable"] } +tauri = { version = "2.0.0-rc", features = ["unstable", "tray-icon"] } tauri-plugin-shell = "2.0.0-rc" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/wallitor-gui/src-tauri/src/lib.rs b/wallitor-gui/src-tauri/src/lib.rs index 3e3bb08..8ee52c0 100644 --- a/wallitor-gui/src-tauri/src/lib.rs +++ b/wallitor-gui/src-tauri/src/lib.rs @@ -1,127 +1,175 @@ -mod setup; mod reader; +mod setup; -use std::{fs, path}; -use serde_json::{self,json}; -use std::path::Path; -use tauri::ipc::Response; -use serde::{Deserialize,Serialize}; use chrono::Local; -use libloading::{Library,Symbol}; +use libloading::{Library, Symbol}; +use serde::{Deserialize, Serialize}; +use serde_json::{self, json}; use std::ffi::CString; +use std::path::Path; +use std::{fs, path}; +use tauri::ipc::Response; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - tauri::Builder::default() - .setup(setup::init) - .plugin(tauri_plugin_fs::init()) - .plugin(tauri_plugin_dialog::init()) - .invoke_handler(tauri::generate_handler![read_resource_dir,get_file,new_wallpaper,set_wallpaper]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + tauri::Builder::default() + .setup(setup::init) + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_dialog::init()) + .invoke_handler(tauri::generate_handler![ + read_resource_dir, + get_file, + new_wallpaper, + set_wallpaper, + del_folder + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } #[tauri::command] async fn read_resource_dir() -> String { - let mut file_map = reader::FileMap::new(); - let path = Path::new("./resource"); - if let Ok(false) = fs::exists(path){ - fs::create_dir(path).expect("Can't create dir"); - } - file_map.read_resourse_directory(path).expect("Can't read dir"); - serde_json::to_string(&file_map).unwrap() + let mut file_map = reader::FileMap::new(); + let path = Path::new(".\\resource"); + if let Ok(false) = fs::exists(path) { + fs::create_dir(path).expect("Can't create dir"); + } + file_map + .read_resourse_directory(path) + .expect("Can't read dir"); + serde_json::to_string(&file_map).unwrap() } // remember to call `.manage(MyState::default())` #[tauri::command] -async fn get_file(path:String) -> Response{ - let p = path::Path::new(&path); - if let Ok(true) = fs::exists(p){ - let data: Vec = fs::read(p).unwrap(); - return tauri::ipc::Response::new(data); - } - tauri::ipc::Response::new(String::from("")) +async fn get_file(path: String) -> Response { + let p = Path::new(&path); + if p.starts_with(".\\") { + if let Ok(true) = fs::exists(p) { + let data: Vec = fs::read(p).unwrap(); + return tauri::ipc::Response::new(data); + } + } + tauri::ipc::Response::new(String::from("")) +} + +#[tauri::command] +async fn del_folder(path: String) -> bool { + let p = Path::new(&path); + if p.starts_with(".\\") { + if p.is_dir() { + if let Ok(true) = fs::exists(p) { + if fs::remove_dir_all(p).is_ok() { + return true; + } + } + } + } + false } #[derive(Deserialize)] struct AddInfo { - name: String, - preview: String, - media: String, - description: String + name: String, + preview: String, + media: String, + description: String, } #[derive(Serialize)] -struct Info{ - media_type:String, - description:String, - created:i64, - entry_point:String +struct Info { + media_type: String, + description: String, + created: i64, + entry_point: String, } #[derive(Serialize)] -struct Opt{ - mute:bool +struct Opt { + mute: bool, } #[derive(Serialize)] -struct Config{ - name:String, - info:Info, - option:Opt -} - - -#[tauri::command] -async fn new_wallpaper(info:AddInfo) -> String{ - let base_url = String::from("./resource"); - let current_time:i64 = Local::now().timestamp(); - let folder = format!("{}/{}",base_url,current_time); - if fs::create_dir_all(Path::new(&folder)).is_err(){ - return String::from("Error creating folder."); - } - if let Ok(false) = fs::exists(Path::new(&info.preview)){ - return String::from("Source Image doesn't exist."); - } - if fs::copy(Path::new(&info.preview), Path::new(&format!("{}/preview.jpg",folder))).is_err(){ - return String::from("Error copy image."); - } - if fs::create_dir(Path::new(&format!("{}/res",folder))).is_err(){ - return String::from("Error creating res folder."); - } - let media = Path::new(&info.media); - if let Some(filename) = media.file_name().and_then(|f| f.to_str()){ - if fs::copy(Path::new(&info.media), Path::new(&format!("{}/res/{}",folder,filename))).is_err(){ - return String::from("Error copy media."); - } - let config =json!( Config{ - name:info.name, - info:Info { media_type: String::from("video"), description: info.description, created: current_time,entry_point:String::from(filename)}, - option:Opt{mute:true} - }); - if fs::write(Path::new(&format!("{}/config.json",folder)), config.to_string()).is_err(){ - return String::from("Error write config."); - } - } - else{ - return String::from("Invalid media path."); - } - String::from("Success") +struct Config { + name: String, + info: Info, + option: Opt, } #[tauri::command] -async fn set_wallpaper(title:String)->bool{ - let lib = unsafe { - Library::new("wallitor-core.dll").unwrap() - }; - type SetFn = unsafe extern "C" fn(*const i8)->i8; - let set:Symbol = unsafe { - lib.get(b"set_wallpaper\0").unwrap() - }; - let title = CString::new(title.to_string()).unwrap(); - unsafe { - let res = set(title.as_ptr()); - if res == 0 {return false;}; - } - return true; -} \ No newline at end of file +async fn new_wallpaper(info: AddInfo) -> String { + let base_url = String::from("./resource"); + let current_time: i64 = Local::now().timestamp(); + let folder = format!("{}/{}", base_url, current_time); + if fs::create_dir_all(Path::new(&folder)).is_err() { + return String::from("Error creating folder."); + } + if !info.preview.is_empty() { + if let Ok(false) = fs::exists(Path::new(&info.preview)) { + return String::from("Source Image doesn't exist."); + } + let preview = Path::new(&info.preview); + if let Some(ext) = preview.extension().and_then(|f| f.to_str()) { + if fs::copy( + Path::new(&info.preview), + Path::new(&format!("{}/preview.{}", folder, ext)), + ) + .is_err() + { + return String::from("Error copy image."); + } + } else { + return String::from("Invalid Image Path."); + } + } + if fs::create_dir(Path::new(&format!("{}/res", folder))).is_err() { + return String::from("Error creating res folder."); + } + let media = Path::new(&info.media); + if let Some(filename) = media.file_name().and_then(|f| f.to_str()) { + if fs::copy( + Path::new(&info.media), + Path::new(&format!("{}/res/{}", folder, filename)), + ) + .is_err() + { + return String::from("Error copy media."); + } + let config = json!(Config { + name: info.name, + info: Info { + media_type: String::from("video"), + description: info.description, + created: current_time, + entry_point: String::from(filename) + }, + option: Opt { mute: true } + }); + if fs::write( + Path::new(&format!("{}/config.json", folder)), + config.to_string(), + ) + .is_err() + { + return String::from("Error write config."); + } + } else { + return String::from("Invalid media path."); + } + String::from("Success") +} + +#[tauri::command] +async fn set_wallpaper(title: String) -> bool { + let lib = unsafe { Library::new("wallitor-core.dll").unwrap() }; + type SetFn = unsafe extern "C" fn(*const i8) -> i8; + let set: Symbol = unsafe { lib.get(b"set_wallpaper\0").unwrap() }; + let title = CString::new(title.to_string()).unwrap(); + unsafe { + let res = set(title.as_ptr()); + if res == 0 { + return false; + }; + } + return true; +} diff --git a/wallitor-gui/src-tauri/src/setup.rs b/wallitor-gui/src-tauri/src/setup.rs index a6e735e..90dd468 100644 --- a/wallitor-gui/src-tauri/src/setup.rs +++ b/wallitor-gui/src-tauri/src/setup.rs @@ -1,15 +1,56 @@ +use tauri::{ + menu::{Menu, MenuItem}, + tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, +}; use tauri::{App, Manager}; #[cfg(target_os = "windows")] use window_vibrancy; - /// setup pub fn init(app: &mut App) -> std::result::Result<(), Box> { let win = app.get_webview_window("main").unwrap(); // 仅在 windows 下执行 #[cfg(target_os = "windows")] - if let Err(_) = window_vibrancy::apply_mica(&win, None){ - window_vibrancy::apply_acrylic(&win, Some((10,10,10,210))).expect("Unsupport Blur Effect!") + if let Err(_) = window_vibrancy::apply_mica(&win, None) { + window_vibrancy::apply_acrylic(&win, Some((10, 10, 10, 210))) + .expect("Unsupport Blur Effect!") } + let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?; + let menu = Menu::with_items(app, &[&quit_i])?; + TrayIconBuilder::new() + .icon(app.default_window_icon().unwrap().clone()) + .menu(&menu) + .on_menu_event(|app, event| match event.id.as_ref() { + "quit" => { + app.exit(0); + } + _ => {} + }) + .on_tray_icon_event(move |tray, event| match event { + TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } + | TrayIconEvent::DoubleClick { + button: MouseButton::Left, + .. + } => { + let app = tray.app_handle(); + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + } + } + TrayIconEvent::Click { + button: MouseButton::Right, + button_state: MouseButtonState::Up, + .. + } => { + let _ = menu.app_handle().show_menu(); + } + _ => {} + }) + .build(app)?; Ok(()) -} \ No newline at end of file +} diff --git a/wallitor-gui/src/assets/svgs/delete.svg b/wallitor-gui/src/assets/svgs/delete.svg new file mode 100644 index 0000000..786d82a --- /dev/null +++ b/wallitor-gui/src/assets/svgs/delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/wallitor-gui/src/components/AddItem.vue b/wallitor-gui/src/components/AddItem.vue index 36d0b12..c5e5e3d 100644 --- a/wallitor-gui/src/components/AddItem.vue +++ b/wallitor-gui/src/components/AddItem.vue @@ -82,6 +82,13 @@ const addInfo = ref({ }) const image_src = ref(""); const support_ext = [".mp4", ".mkv", ".flv", ".ts"]; +const support_img_ext = [".jpg", ".png", ".gif", ".webp"]; +const support_img_ext_map: { [key in (typeof support_img_ext)[number]]: string } = { + '.jpg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.webp': 'image/webp' +} defineExpose({ open }) @@ -99,7 +106,10 @@ function selectMedia() { let ext = file.substring(file.lastIndexOf(".")); if (support_ext.includes(ext)) { addInfo.value.media = file; - } + } else ElMessage({ + type: "error", + message: "文件格式不受支持" + }) } }) } @@ -107,14 +117,20 @@ function selectMedia() { function selectPreview() { handleFileOpen().then((file) => { if (file) { - addInfo.value.preview = file; - invoke("get_file", { - path: file - }).then((res) => { - let binary_data_arr = new Uint8Array(res as number[]); - const blob = new Blob([binary_data_arr], { type: 'image/jpeg' }); - const imageUrl = URL.createObjectURL(blob); - image_src.value = imageUrl; + let ext = file.substring(file.lastIndexOf(".")); + if (support_img_ext.includes(ext)) { + addInfo.value.preview = file; + invoke("get_file", { + path: file + }).then((res) => { + let binary_data_arr = new Uint8Array(res as number[]); + const blob = new Blob([binary_data_arr], { type: support_img_ext_map[ext] }); + const imageUrl = URL.createObjectURL(blob); + image_src.value = imageUrl; + }) + } else ElMessage({ + type: "error", + message: "文件格式不受支持" }) } }) @@ -124,8 +140,13 @@ function toggleVisible() { visible.value = !visible.value } +function checkInfo(info: AddInfo) { + if (!info.name || !info.media) return false + else return true; +} + function handleAdd() { - invoke("new_wallpaper", { + if (checkInfo(addInfo.value)) invoke("new_wallpaper", { info: addInfo.value }).then((res) => { if (res as string == "Success") { @@ -135,6 +156,7 @@ function handleAdd() { media: "", description: "" } + image_src.value = ""; store.commit("getWpList"); toggleVisible(); ElMessage({ @@ -147,6 +169,10 @@ function handleAdd() { message: `新建失败 ${res}` }) }) + else ElMessage({ + type: "error", + message: "请填写名称并选择媒体文件" + }) } diff --git a/wallitor-gui/src/components/ApplyBar.vue b/wallitor-gui/src/components/ApplyBar.vue index 1b6e984..f6294a5 100644 --- a/wallitor-gui/src/components/ApplyBar.vue +++ b/wallitor-gui/src/components/ApplyBar.vue @@ -170,7 +170,7 @@ function apply() { } .apply-bar-mask { - z-index: 500; + z-index: 200; position: absolute; top: 0; left: 0; diff --git a/wallitor-gui/src/components/CDialog.vue b/wallitor-gui/src/components/CDialog.vue index 6bc93e9..80a81d5 100644 --- a/wallitor-gui/src/components/CDialog.vue +++ b/wallitor-gui/src/components/CDialog.vue @@ -65,7 +65,7 @@ export default defineComponent({ diff --git a/wallitor-gui/src/components/CRMenuCell.vue b/wallitor-gui/src/components/CRMenuCell.vue new file mode 100644 index 0000000..ab0e184 --- /dev/null +++ b/wallitor-gui/src/components/CRMenuCell.vue @@ -0,0 +1,37 @@ + + + \ No newline at end of file diff --git a/wallitor-gui/src/components/ItemCard.vue b/wallitor-gui/src/components/ItemCard.vue index 8fc0a4a..e9070ee 100644 --- a/wallitor-gui/src/components/ItemCard.vue +++ b/wallitor-gui/src/components/ItemCard.vue @@ -1,7 +1,7 @@