A photo manager for VRChat.

Fixed clipboard

Changed files
+147 -47
src
Components
src-tauri
+2 -1
changelog
··· 94 v0.2.5: 95 - Fixed the "Start with windows" button appearing on linux 96 - Fixed linux tray icon title 97 - - Fixed build-release.sh script outputting the wrong names for windows builds
··· 94 v0.2.5: 95 - Fixed the "Start with windows" button appearing on linux 96 - Fixed linux tray icon title 97 + - Fixed build-release.sh script outputting the wrong names for windows builds 98 + - Fixed clipboard on linux ( and speeded it up on windows )
+88 -6
src-tauri/Cargo.lock
··· 6 name = "VRChatPhotoManager" 7 version = "0.2.5" 8 dependencies = [ 9 "dirs 5.0.1", 10 "fast_image_resize", 11 "image", ··· 101 checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" 102 103 [[package]] 104 name = "arg_enum_proc_macro" 105 version = "0.3.4" 106 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 179 "futures-lite", 180 "parking", 181 "polling", 182 - "rustix", 183 "slab", 184 "windows-sys 0.60.2", 185 ] ··· 210 "cfg-if", 211 "event-listener", 212 "futures-lite", 213 - "rustix", 214 ] 215 216 [[package]] ··· 236 "cfg-if", 237 "futures-core", 238 "futures-io", 239 - "rustix", 240 "signal-hook-registry", 241 "slab", 242 "windows-sys 0.60.2", ··· 599 "num-traits", 600 "serde", 601 "windows-link", 602 ] 603 604 [[package]] ··· 1176 ] 1177 1178 [[package]] 1179 name = "event-listener" 1180 version = "5.4.0" 1181 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1553 ] 1554 1555 [[package]] 1556 name = "getrandom" 1557 version = "0.1.16" 1558 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2473 "libc", 2474 "redox_syscall", 2475 ] 2476 2477 [[package]] 2478 name = "linux-raw-sys" ··· 3438 "concurrent-queue", 3439 "hermit-abi", 3440 "pin-project-lite", 3441 - "rustix", 3442 "windows-sys 0.60.2", 3443 ] 3444 ··· 4029 4030 [[package]] 4031 name = "rustix" 4032 version = "1.0.8" 4033 source = "registry+https://github.com/rust-lang/crates.io-index" 4034 checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" ··· 4036 "bitflags 2.9.1", 4037 "errno", 4038 "libc", 4039 - "linux-raw-sys", 4040 "windows-sys 0.60.2", 4041 ] 4042 ··· 5090 "fastrand", 5091 "getrandom 0.3.3", 5092 "once_cell", 5093 - "rustix", 5094 "windows-sys 0.59.0", 5095 ] 5096 ··· 6458 "once_cell", 6459 "pkg-config", 6460 ] 6461 6462 [[package]] 6463 name = "xdg-home"
··· 6 name = "VRChatPhotoManager" 7 version = "0.2.5" 8 dependencies = [ 9 + "arboard", 10 "dirs 5.0.1", 11 "fast_image_resize", 12 "image", ··· 102 checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" 103 104 [[package]] 105 + name = "arboard" 106 + version = "3.6.0" 107 + source = "registry+https://github.com/rust-lang/crates.io-index" 108 + checksum = "55f533f8e0af236ffe5eb979b99381df3258853f00ba2e44b6e1955292c75227" 109 + dependencies = [ 110 + "clipboard-win", 111 + "image", 112 + "log", 113 + "objc2 0.6.1", 114 + "objc2-app-kit", 115 + "objc2-core-foundation", 116 + "objc2-core-graphics", 117 + "objc2-foundation 0.3.1", 118 + "parking_lot", 119 + "percent-encoding", 120 + "windows-sys 0.59.0", 121 + "x11rb", 122 + ] 123 + 124 + [[package]] 125 name = "arg_enum_proc_macro" 126 version = "0.3.4" 127 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 200 "futures-lite", 201 "parking", 202 "polling", 203 + "rustix 1.0.8", 204 "slab", 205 "windows-sys 0.60.2", 206 ] ··· 231 "cfg-if", 232 "event-listener", 233 "futures-lite", 234 + "rustix 1.0.8", 235 ] 236 237 [[package]] ··· 257 "cfg-if", 258 "futures-core", 259 "futures-io", 260 + "rustix 1.0.8", 261 "signal-hook-registry", 262 "slab", 263 "windows-sys 0.60.2", ··· 620 "num-traits", 621 "serde", 622 "windows-link", 623 + ] 624 + 625 + [[package]] 626 + name = "clipboard-win" 627 + version = "5.4.1" 628 + source = "registry+https://github.com/rust-lang/crates.io-index" 629 + checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" 630 + dependencies = [ 631 + "error-code", 632 ] 633 634 [[package]] ··· 1206 ] 1207 1208 [[package]] 1209 + name = "error-code" 1210 + version = "3.3.2" 1211 + source = "registry+https://github.com/rust-lang/crates.io-index" 1212 + checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" 1213 + 1214 + [[package]] 1215 name = "event-listener" 1216 version = "5.4.0" 1217 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1589 ] 1590 1591 [[package]] 1592 + name = "gethostname" 1593 + version = "0.4.3" 1594 + source = "registry+https://github.com/rust-lang/crates.io-index" 1595 + checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" 1596 + dependencies = [ 1597 + "libc", 1598 + "windows-targets 0.48.5", 1599 + ] 1600 + 1601 + [[package]] 1602 name = "getrandom" 1603 version = "0.1.16" 1604 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2519 "libc", 2520 "redox_syscall", 2521 ] 2522 + 2523 + [[package]] 2524 + name = "linux-raw-sys" 2525 + version = "0.4.15" 2526 + source = "registry+https://github.com/rust-lang/crates.io-index" 2527 + checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 2528 2529 [[package]] 2530 name = "linux-raw-sys" ··· 3490 "concurrent-queue", 3491 "hermit-abi", 3492 "pin-project-lite", 3493 + "rustix 1.0.8", 3494 "windows-sys 0.60.2", 3495 ] 3496 ··· 4081 4082 [[package]] 4083 name = "rustix" 4084 + version = "0.38.44" 4085 + source = "registry+https://github.com/rust-lang/crates.io-index" 4086 + checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 4087 + dependencies = [ 4088 + "bitflags 2.9.1", 4089 + "errno", 4090 + "libc", 4091 + "linux-raw-sys 0.4.15", 4092 + "windows-sys 0.59.0", 4093 + ] 4094 + 4095 + [[package]] 4096 + name = "rustix" 4097 version = "1.0.8" 4098 source = "registry+https://github.com/rust-lang/crates.io-index" 4099 checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" ··· 4101 "bitflags 2.9.1", 4102 "errno", 4103 "libc", 4104 + "linux-raw-sys 0.9.4", 4105 "windows-sys 0.60.2", 4106 ] 4107 ··· 5155 "fastrand", 5156 "getrandom 0.3.3", 5157 "once_cell", 5158 + "rustix 1.0.8", 5159 "windows-sys 0.59.0", 5160 ] 5161 ··· 6523 "once_cell", 6524 "pkg-config", 6525 ] 6526 + 6527 + [[package]] 6528 + name = "x11rb" 6529 + version = "0.13.1" 6530 + source = "registry+https://github.com/rust-lang/crates.io-index" 6531 + checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" 6532 + dependencies = [ 6533 + "gethostname", 6534 + "rustix 0.38.44", 6535 + "x11rb-protocol", 6536 + ] 6537 + 6538 + [[package]] 6539 + name = "x11rb-protocol" 6540 + version = "0.13.1" 6541 + source = "registry+https://github.com/rust-lang/crates.io-index" 6542 + checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" 6543 6544 [[package]] 6545 name = "xdg-home"
+1
src-tauri/Cargo.toml
··· 26 tauri-plugin-process = "2.0.0-rc.0" 27 image = "0.25.2" 28 fast_image_resize = { version = "4.2.1", features = [ "image" ] } 29 30 [target.'cfg(windows)'.dependencies] 31 mslnk = { version = "0.1.8" }
··· 26 tauri-plugin-process = "2.0.0-rc.0" 27 image = "0.25.2" 28 fast_image_resize = { version = "4.2.1", features = [ "image" ] } 29 + arboard = "3.6.0" 30 31 [target.'cfg(windows)'.dependencies] 32 mslnk = { version = "0.1.8" }
+25
src-tauri/src/frontend_calls/copy_image.rs
···
··· 1 + use std::{borrow::Cow, fs::{self, File}, io::BufReader, sync::Mutex}; 2 + 3 + use arboard::{Clipboard, ImageData}; 4 + use image::{codecs::png::PngDecoder, EncodableLayout, GenericImageView, ImageDecoder}; 5 + use tauri::State; 6 + 7 + use crate::{ frontend_calls::get_user_photos_path::get_user_photos_path, pngmeta::PNGImage, util::cache::Cache }; 8 + 9 + #[tauri::command] 10 + pub fn copy_image( path: String, clipboard: State<Mutex<Clipboard>>, cache: State<Cache> ) { 11 + let path = format!("{}/{}", get_user_photos_path(cache), path); 12 + println!("Copying Image: {}", &path); 13 + 14 + let img = image::open(path).unwrap(); 15 + let size = img.dimensions(); 16 + 17 + let img_data = ImageData { 18 + width: size.0 as usize, 19 + height: size.1 as usize, 20 + bytes: Cow::from(img.into_rgba8().to_vec()) 21 + }; 22 + 23 + let mut lock = clipboard.lock().unwrap(); 24 + lock.set_image(img_data).unwrap(); 25 + }
+1
src-tauri/src/frontend_calls/mod.rs
··· 13 pub mod start_user_auth; 14 pub mod start_with_win; 15 pub mod sync_photos;
··· 13 pub mod start_user_auth; 14 pub mod start_with_win; 15 pub mod sync_photos; 16 + pub mod copy_image;
+7 -2
src-tauri/src/main.rs
··· 7 mod worldscraper; 8 9 use core::time; 10 use frontend_calls::*; 11 12 use notify::{ EventKind, RecursiveMode, Watcher }; 13 use pngmeta::PNGImage; 14 use regex::Regex; 15 use util::{ cache::Cache, get_photo_path::get_photo_path }; 16 - use std::{ env, fs, thread }; 17 use tauri::{ Emitter, Manager, State, WindowEvent }; 18 use tauri_plugin_deep_link::DeepLinkExt; 19 ··· 142 ) 143 .unwrap(); 144 145 tauri::Builder::default() 146 .plugin(tauri_plugin_single_instance::init(| app, argv, _cwd | { 147 app.get_webview_window("main").unwrap().show().unwrap(); ··· 163 _ => {} 164 }) 165 .manage(cache) 166 .setup(|app| { 167 let handle = app.handle(); 168 ··· 209 config::get_config_value_string, 210 config::set_config_value_int, 211 config::get_config_value_int, 212 - get_os::get_os 213 ]) 214 .run(tauri::generate_context!()) 215 .expect("error while running tauri application");
··· 7 mod worldscraper; 8 9 use core::time; 10 + use arboard::Clipboard; 11 use frontend_calls::*; 12 13 use notify::{ EventKind, RecursiveMode, Watcher }; 14 use pngmeta::PNGImage; 15 use regex::Regex; 16 use util::{ cache::Cache, get_photo_path::get_photo_path }; 17 + use std::{ env, fs, sync::Mutex, thread }; 18 use tauri::{ Emitter, Manager, State, WindowEvent }; 19 use tauri_plugin_deep_link::DeepLinkExt; 20 ··· 143 ) 144 .unwrap(); 145 146 + let clipboard = Clipboard::new().unwrap(); 147 + 148 tauri::Builder::default() 149 .plugin(tauri_plugin_single_instance::init(| app, argv, _cwd | { 150 app.get_webview_window("main").unwrap().show().unwrap(); ··· 166 _ => {} 167 }) 168 .manage(cache) 169 + .manage(Mutex::new(clipboard)) 170 .setup(|app| { 171 let handle = app.handle(); 172 ··· 213 config::get_config_value_string, 214 config::set_config_value_int, 215 config::get_config_value_int, 216 + get_os::get_os, 217 + copy_image::copy_image 218 ]) 219 .run(tauri::generate_context!()) 220 .expect("error while running tauri application");
+9 -9
src-tauri/src/pngmeta.rs
··· 3 4 #[derive(Clone)] 5 pub struct PNGImage { 6 - width: u32, 7 - height: u32, 8 - bit_depth: u8, 9 - colour_type: u8, 10 - compression_method: u8, 11 - filter_method: u8, 12 - interlace_method: u8, 13 - metadata: String, 14 - path: String, 15 } 16 17 impl PNGImage {
··· 3 4 #[derive(Clone)] 5 pub struct PNGImage { 6 + pub width: u32, 7 + pub height: u32, 8 + pub bit_depth: u8, 9 + pub colour_type: u8, 10 + pub compression_method: u8, 11 + pub filter_method: u8, 12 + pub interlace_method: u8, 13 + pub metadata: String, 14 + pub path: String, 15 } 16 17 impl PNGImage {
+14 -29
src/Components/PhotoViewer.tsx
··· 84 } 85 86 let copyImage = () => { 87 - let canvas = document.createElement('canvas'); 88 - let ctx = canvas.getContext('2d')!; 89 - 90 - canvas.width = window.PhotoViewerManager.CurrentPhoto()?.width || 0; 91 - canvas.height = window.PhotoViewerManager.CurrentPhoto()?.height || 0; 92 - 93 - ctx.drawImage(imageViewer, 0, 0); 94 - 95 - canvas.toBlob(( blob ) => { 96 - navigator.clipboard.write([ 97 - new ClipboardItem({ 98 - 'image/png': blob! 99 - }) 100 - ]); 101 - 102 - canvas.remove(); 103 - 104 - anime.set('.copy-notif', { translateX: '-50%', translateY: '-100px' }); 105 - anime({ 106 - targets: '.copy-notif', 107 - opacity: 1, 108 - translateY: '0px' 109 - }); 110 - 111 - setTimeout(() => { 112 anime({ 113 targets: '.copy-notif', 114 - opacity: 0, 115 - translateY: '-100px' 116 }); 117 - }, 2000); 118 - }); 119 } 120 121 let closeTray = () => {
··· 84 } 85 86 let copyImage = () => { 87 + invoke('copy_image', { path: window.PhotoViewerManager.CurrentPhoto()!.path }) 88 + .then(() => { 89 + anime.set('.copy-notif', { translateX: '-50%', translateY: '-100px' }); 90 anime({ 91 targets: '.copy-notif', 92 + opacity: 1, 93 + translateY: '0px' 94 }); 95 + 96 + setTimeout(() => { 97 + anime({ 98 + targets: '.copy-notif', 99 + opacity: 0, 100 + translateY: '-100px' 101 + }); 102 + }, 2000); 103 + }) 104 } 105 106 let closeTray = () => {