A photo manager for VRChat.

Fixed clipboard

Changed files
+147 -47
src
Components
src-tauri
+2 -1
changelog
··· 94 94 v0.2.5: 95 95 - Fixed the "Start with windows" button appearing on linux 96 96 - Fixed linux tray icon title 97 - - Fixed build-release.sh script outputting the wrong names for windows builds 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 6 name = "VRChatPhotoManager" 7 7 version = "0.2.5" 8 8 dependencies = [ 9 + "arboard", 9 10 "dirs 5.0.1", 10 11 "fast_image_resize", 11 12 "image", ··· 101 102 checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" 102 103 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]] 104 125 name = "arg_enum_proc_macro" 105 126 version = "0.3.4" 106 127 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 179 200 "futures-lite", 180 201 "parking", 181 202 "polling", 182 - "rustix", 203 + "rustix 1.0.8", 183 204 "slab", 184 205 "windows-sys 0.60.2", 185 206 ] ··· 210 231 "cfg-if", 211 232 "event-listener", 212 233 "futures-lite", 213 - "rustix", 234 + "rustix 1.0.8", 214 235 ] 215 236 216 237 [[package]] ··· 236 257 "cfg-if", 237 258 "futures-core", 238 259 "futures-io", 239 - "rustix", 260 + "rustix 1.0.8", 240 261 "signal-hook-registry", 241 262 "slab", 242 263 "windows-sys 0.60.2", ··· 599 620 "num-traits", 600 621 "serde", 601 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", 602 632 ] 603 633 604 634 [[package]] ··· 1176 1206 ] 1177 1207 1178 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]] 1179 1215 name = "event-listener" 1180 1216 version = "5.4.0" 1181 1217 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1553 1589 ] 1554 1590 1555 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]] 1556 1602 name = "getrandom" 1557 1603 version = "0.1.16" 1558 1604 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2473 2519 "libc", 2474 2520 "redox_syscall", 2475 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" 2476 2528 2477 2529 [[package]] 2478 2530 name = "linux-raw-sys" ··· 3438 3490 "concurrent-queue", 3439 3491 "hermit-abi", 3440 3492 "pin-project-lite", 3441 - "rustix", 3493 + "rustix 1.0.8", 3442 3494 "windows-sys 0.60.2", 3443 3495 ] 3444 3496 ··· 4029 4081 4030 4082 [[package]] 4031 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" 4032 4097 version = "1.0.8" 4033 4098 source = "registry+https://github.com/rust-lang/crates.io-index" 4034 4099 checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" ··· 4036 4101 "bitflags 2.9.1", 4037 4102 "errno", 4038 4103 "libc", 4039 - "linux-raw-sys", 4104 + "linux-raw-sys 0.9.4", 4040 4105 "windows-sys 0.60.2", 4041 4106 ] 4042 4107 ··· 5090 5155 "fastrand", 5091 5156 "getrandom 0.3.3", 5092 5157 "once_cell", 5093 - "rustix", 5158 + "rustix 1.0.8", 5094 5159 "windows-sys 0.59.0", 5095 5160 ] 5096 5161 ··· 6458 6523 "once_cell", 6459 6524 "pkg-config", 6460 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" 6461 6543 6462 6544 [[package]] 6463 6545 name = "xdg-home"
+1
src-tauri/Cargo.toml
··· 26 26 tauri-plugin-process = "2.0.0-rc.0" 27 27 image = "0.25.2" 28 28 fast_image_resize = { version = "4.2.1", features = [ "image" ] } 29 + arboard = "3.6.0" 29 30 30 31 [target.'cfg(windows)'.dependencies] 31 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 13 pub mod start_user_auth; 14 14 pub mod start_with_win; 15 15 pub mod sync_photos; 16 + pub mod copy_image;
+7 -2
src-tauri/src/main.rs
··· 7 7 mod worldscraper; 8 8 9 9 use core::time; 10 + use arboard::Clipboard; 10 11 use frontend_calls::*; 11 12 12 13 use notify::{ EventKind, RecursiveMode, Watcher }; 13 14 use pngmeta::PNGImage; 14 15 use regex::Regex; 15 16 use util::{ cache::Cache, get_photo_path::get_photo_path }; 16 - use std::{ env, fs, thread }; 17 + use std::{ env, fs, sync::Mutex, thread }; 17 18 use tauri::{ Emitter, Manager, State, WindowEvent }; 18 19 use tauri_plugin_deep_link::DeepLinkExt; 19 20 ··· 142 143 ) 143 144 .unwrap(); 144 145 146 + let clipboard = Clipboard::new().unwrap(); 147 + 145 148 tauri::Builder::default() 146 149 .plugin(tauri_plugin_single_instance::init(| app, argv, _cwd | { 147 150 app.get_webview_window("main").unwrap().show().unwrap(); ··· 163 166 _ => {} 164 167 }) 165 168 .manage(cache) 169 + .manage(Mutex::new(clipboard)) 166 170 .setup(|app| { 167 171 let handle = app.handle(); 168 172 ··· 209 213 config::get_config_value_string, 210 214 config::set_config_value_int, 211 215 config::get_config_value_int, 212 - get_os::get_os 216 + get_os::get_os, 217 + copy_image::copy_image 213 218 ]) 214 219 .run(tauri::generate_context!()) 215 220 .expect("error while running tauri application");
+9 -9
src-tauri/src/pngmeta.rs
··· 3 3 4 4 #[derive(Clone)] 5 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, 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 15 } 16 16 17 17 impl PNGImage {
+14 -29
src/Components/PhotoViewer.tsx
··· 84 84 } 85 85 86 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(() => { 87 + invoke('copy_image', { path: window.PhotoViewerManager.CurrentPhoto()!.path }) 88 + .then(() => { 89 + anime.set('.copy-notif', { translateX: '-50%', translateY: '-100px' }); 112 90 anime({ 113 91 targets: '.copy-notif', 114 - opacity: 0, 115 - translateY: '-100px' 92 + opacity: 1, 93 + translateY: '0px' 116 94 }); 117 - }, 2000); 118 - }); 95 + 96 + setTimeout(() => { 97 + anime({ 98 + targets: '.copy-notif', 99 + opacity: 0, 100 + translateY: '-100px' 101 + }); 102 + }, 2000); 103 + }) 119 104 } 120 105 121 106 let closeTray = () => {