feat: image copy and paste

This commit is contained in:
electria 2026-07-31 19:36:58 -07:00
commit 0840dd440d
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
4 changed files with 152 additions and 2 deletions

78
Cargo.lock generated
View file

@ -136,6 +136,27 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
[[package]]
name = "arboard"
version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
dependencies = [
"clipboard-win",
"image",
"log",
"objc2 0.6.4",
"objc2-app-kit 0.3.2",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-foundation 0.3.2",
"parking_lot",
"percent-encoding",
"windows-sys 0.59.0",
"wl-clipboard-rs",
"x11rb",
]
[[package]] [[package]]
name = "arg_enum_proc_macro" name = "arg_enum_proc_macro"
version = "0.3.4" version = "0.3.4"
@ -1226,6 +1247,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.9" version = "1.1.9"
@ -1900,6 +1927,7 @@ dependencies = [
name = "imagey" name = "imagey"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"arboard",
"dirs", "dirs",
"iced", "iced",
"image", "image",
@ -3065,6 +3093,16 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "os_pipe"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "owned_ttf_parser" name = "owned_ttf_parser"
version = "0.25.1" version = "0.25.1"
@ -3131,6 +3169,17 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "petgraph"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
dependencies = [
"fixedbitset",
"hashbrown 0.15.5",
"indexmap",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.13" version = "1.1.13"
@ -4253,6 +4302,17 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "tree_magic_mini"
version = "3.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
dependencies = [
"memchr",
"nom",
"petgraph",
]
[[package]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.25.1" version = "0.25.1"
@ -5116,6 +5176,24 @@ version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wl-clipboard-rs"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
dependencies = [
"libc",
"log",
"os_pipe",
"rustix 1.1.4",
"thiserror 2.0.18",
"tree_magic_mini",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-protocols-wlr",
]
[[package]] [[package]]
name = "x11-dl" name = "x11-dl"
version = "2.21.0" version = "2.21.0"

View file

@ -22,3 +22,7 @@ features = [ "avif-native" ]
version = "0.12.6" version = "0.12.6"
# I really wish the image crate was named better # I really wish the image crate was named better
features = [ "image" ] features = [ "image" ]
[dependencies.arboard]
version = "3.6.1"
features = [ "wayland-data-control" ]

View file

@ -6,6 +6,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use arboard::Clipboard;
use iced::{ use iced::{
Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, event, Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, event,
keyboard::{self, Key, key}, keyboard::{self, Key, key},
@ -26,7 +27,7 @@ enum Message {
Event(iced::Event), Event(iced::Event),
} }
#[derive(Clone, Debug, Default)] #[derive(Default)]
struct State { struct State {
image: Option<RgbaImage>, image: Option<RgbaImage>,
image_display: Option<widget::image::Allocation>, image_display: Option<widget::image::Allocation>,
@ -35,6 +36,8 @@ struct State {
error: Option<String>, error: Option<String>,
info: Option<String>, info: Option<String>,
infobar_shown: bool, infobar_shown: bool,
clipboard: Option<Clipboard>,
} }
impl State { impl State {
fn new() -> (Self, Task<Message>) { fn new() -> (Self, Task<Message>) {
@ -166,6 +169,52 @@ impl State {
Key::Character("q") => return window::latest().and_then(window::close), Key::Character("q") => return window::latest().and_then(window::close),
Key::Character("y") | Key::Character("c") => {
if let Some(image) = self.image.as_ref() {
match match self.clipboard.take() {
Some(c) => Ok(c),
None => Clipboard::new(),
} {
Ok(mut clipboard) => {
self.error = clipboard
.set_image(utils::arboard_from_rgbaimage(image))
.map_err(|e| e.to_string())
.err();
self.clipboard = Some(clipboard);
}
Err(e) => self.error = Some(e.to_string()),
}
} else {
self.error = Some("no image to yank".into());
}
}
Key::Character("p") | Key::Character("v") => {
match match self.clipboard.take() {
Some(c) => Ok(c),
None => Clipboard::new(),
} {
Ok(mut clipboard) => {
match clipboard.get_image().map(utils::rgbaimage_from_arboard) {
Ok(image) => {
self.error = None;
self.clipboard = Some(clipboard);
self.info =
Some(format!("{}x{}", image.width(), image.height(),));
if image.dimensions() < (100, 100) {
self.image_filter = widget::image::FilterMethod::Nearest;
}
self.image = Some(image);
return self.allocate_image();
}
Err(e) => self.error = Some(e.to_string()),
};
self.clipboard = Some(clipboard);
}
Err(e) => self.error = Some(e.to_string()),
}
}
// ignore unused keys // ignore unused keys
_ => {} _ => {}
}, },

View file

@ -1,4 +1,7 @@
use std::path::{Path, PathBuf}; use std::{
borrow::Cow,
path::{Path, PathBuf},
};
use iced::widget; use iced::widget;
use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage}; use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage};
@ -52,3 +55,19 @@ pub const fn string_from_filter_type(f: widget::image::FilterMethod) -> &'static
widget::image::FilterMethod::Nearest => "nearest neighbor", widget::image::FilterMethod::Nearest => "nearest neighbor",
} }
} }
pub fn arboard_from_rgbaimage<'a>(image: &'a RgbaImage) -> arboard::ImageData<'a> {
arboard::ImageData {
width: image.width() as usize,
height: image.height() as usize,
bytes: Cow::Borrowed(image.as_ref()),
}
}
pub fn rgbaimage_from_arboard(image: arboard::ImageData) -> RgbaImage {
RgbaImage::from_raw(
image.width as u32,
image.height as u32,
image.bytes.into_owned(),
)
.unwrap()
}