From 4d777b2aa5845011707be9a42fb84c907965f684 Mon Sep 17 00:00:00 2001 From: Logan King Date: Fri, 18 Jul 2025 19:03:06 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20add=20qr=20code=20clipboard=20funct?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 231 ++++++++++++++++++++++++++++++++++++++++++++-------- Cargo.toml | 2 + README.md | 1 + src/main.rs | 61 ++++++++++++++ 4 files changed, 262 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef98f37..be8f031 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,7 +179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" dependencies = [ "android-properties", - "bitflags 2.4.2", + "bitflags 2.9.1", "cc", "cesu8", "jni 0.21.1", @@ -268,6 +268,26 @@ version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +[[package]] +name = "arboard" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55f533f8e0af236ffe5eb979b99381df3258853f00ba2e44b6e1955292c75227" +dependencies = [ + "clipboard-win 5.4.1", + "image 0.25.6", + "log", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "x11rb 0.13.0", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -665,7 +685,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools", @@ -696,9 +716,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "block" @@ -798,6 +818,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.10.1" @@ -810,7 +836,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "log", "polling 3.5.0", "rustix 0.38.31", @@ -984,6 +1010,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "clru" version = "0.6.1" @@ -1626,6 +1661,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", +] + [[package]] name = "dlib" version = "0.5.2" @@ -1774,6 +1819,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "euclid" version = "0.22.9" @@ -1899,11 +1950,11 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18ab822e58e8bc2b89840dc5dde49afe39302e129c60d39c8520200c085404a7" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "fnv", "generational-arena", "glow", - "image", + "image 0.24.9", "imgref", "log", "lru", @@ -2315,7 +2366,7 @@ version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "cfg_aliases 0.1.1", "cgl", "core-foundation 0.9.4", @@ -2446,6 +2497,15 @@ version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "http" version = "1.1.0" @@ -2687,7 +2747,7 @@ dependencies = [ "derive_more", "fontdue", "i-slint-common", - "image", + "image 0.24.9", "itertools", "linked_hash_set", "lyon_extra", @@ -2720,7 +2780,7 @@ dependencies = [ "fontdue", "i-slint-common", "i-slint-core-macros", - "image", + "image 0.24.9", "integer-sqrt", "lyon_algorithms", "lyon_extra", @@ -2903,6 +2963,19 @@ dependencies = [ "tiff", ] +[[package]] +name = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", + "tiff", +] + [[package]] name = "imagesize" version = "0.12.0" @@ -3190,7 +3263,7 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "libc", "redox_syscall 0.4.1", ] @@ -3201,7 +3274,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "libc", ] @@ -3373,7 +3446,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "block", "core-graphics-types", "foreign-types 0.5.0", @@ -3472,7 +3545,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "jni-sys", "log", "ndk-sys 0.5.0+25.2.9519653", @@ -3535,7 +3608,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "cfg-if", "libc", ] @@ -3546,7 +3619,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -3694,7 +3767,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a9c7f0d511a4ce26b078183179dca908171cfc69f88986fe36c5138e1834476" dependencies = [ "objc-sys", - "objc2-encode 4.0.0", + "objc2-encode 4.1.0", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode 4.1.0", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-graphics", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] @@ -3705,9 +3823,31 @@ checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" [[package]] name = "objc2-encode" -version = "4.0.0" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff06a6505cde0766484f38d8479ac8e6d31c66fbc2d5492f65ca8c091456379" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] [[package]] name = "objc_exception" @@ -3795,7 +3935,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "cfg-if", "foreign-types 0.3.2", "libc", @@ -4092,6 +4232,23 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "qrcode-generator" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0051849b5465059b75f59d388c7318aad6554701b74ecf02afc2573b0306c" +dependencies = [ + "html-escape", + "image 0.25.6", + "qrcodegen", +] + +[[package]] +name = "qrcodegen" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" + [[package]] name = "qttypes" version = "0.2.11" @@ -4108,6 +4265,7 @@ name = "quick-assistant" version = "0.1.0" dependencies = [ "anyhow", + "arboard", "async-openai", "async-std", "chrono", @@ -4126,6 +4284,7 @@ dependencies = [ "humantime", "once_cell", "open", + "qrcode-generator", "rdev", "reqwest", "rodio", @@ -4528,7 +4687,7 @@ version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.13", @@ -4616,7 +4775,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "bytemuck", "smallvec", "ttf-parser", @@ -4863,7 +5022,7 @@ version = "0.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3201eba92bca1f83864f5c3a48309bcfee7e0590bebd7826e7ab0a49aa24a750" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "lazy_static", "skia-bindings", "windows 0.52.0", @@ -4940,7 +5099,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "calloop", "calloop-wayland-source", "cursor-icon", @@ -5235,7 +5394,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5582,7 +5741,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "bytes", "futures-util", "http", @@ -5870,6 +6029,12 @@ dependencies = [ "tiny-skia-path", ] +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "utf8parse" version = "0.2.1" @@ -6066,7 +6231,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "rustix 0.38.31", "wayland-backend", "wayland-scanner", @@ -6078,7 +6243,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "cursor-icon", "wayland-backend", ] @@ -6100,7 +6265,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -6112,7 +6277,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -6125,7 +6290,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -6645,7 +6810,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.4.2", + "bitflags 2.9.1", "bytemuck", "calloop", "cfg_aliases 0.1.1", @@ -6853,7 +7018,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.1", "dlib", "log", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index d47b511..4bc967d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,3 +50,5 @@ windows = { version = "0.52.0", features = [ "Win32_Media_Audio_Endpoints", ] } easy_rdev_key = "0.1.0" +qrcode-generator = { version = "5.0.0", features = ["image"] } +arboard = "3.6.0" diff --git a/README.md b/README.md index ddcdaf7..f7e0c92 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ https://github.com/sloganking/quick-assistant/assets/16965931/a0c7469a-2c64-46e5 - 🗑️ **List and kill processes** by voice - 🌐 **Run internet speed tests** - 📋 **Set the clipboard** contents +- 🔳 **Copy text as a QR code image** to the clipboard - ⏱️ **Timers** with alarm sounds - 🎙️ **Change voice** or speaking speed on the fly - 🔕 **Mute/unmute** the voice output diff --git a/src/main.rs b/src/main.rs index 6bcbb45..9e9bba9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ use anyhow::Context; +use arboard::{Clipboard as ImageClipboard, ImageData}; use async_openai::types::{ ChatCompletionFunctionsArgs, ChatCompletionRequestFunctionMessageArgs, ChatCompletionRequestToolMessageArgs, FinishReason, }; use clipboard::{ClipboardContext, ClipboardProvider}; use dotenvy::dotenv; +use qrcode_generator::{to_image_from_str, QrCodeEcc}; use serde_json::json; +use std::borrow::Cow; use std::fs::File; use std::io::{stdout, BufReader, Write}; use std::path::{Path, PathBuf}; @@ -174,6 +177,14 @@ mod tests { } assert!(parse_voice("doesnotexist").is_none()); } + + #[test] + fn qr_image_data_size() { + let img = qr_image_data("hello", 128).expect("image generation"); + assert_eq!(img.width, 128); + assert_eq!(img.height, 128); + assert_eq!(img.bytes.len(), 128 * 128 * 4); + } } /// Creates a temporary file from a byte slice and returns the path to the file. @@ -566,6 +577,23 @@ fn call_fn( } } + "qr_to_clipboard" => { + let args = match serde_json::from_str::(fn_args) { + Ok(json) => json, + Err(e) => return Some(format!("Failed to parse arguments: {}", e)), + }; + + let text = match args["text"].as_str() { + Some(t) => t, + None => return Some("Missing 'text' argument.".to_string()), + }; + + match qr_to_clipboard(text) { + Ok(_) => Some("QR code copied to clipboard.".to_string()), + Err(e) => Some(format!("Failed to create QR code: {}", e)), + } + } + "set_speech_speed" => { let args: serde_json::Value = serde_json::from_str(fn_args).unwrap(); if let Some(speed) = args["speed"].as_f64() { @@ -934,6 +962,29 @@ fn run_get_content_wait_on_file(file_path: &Path) -> Result { } } +fn qr_image_data(text: &str, size: usize) -> Result, String> { + let raw = to_image_from_str(text, QrCodeEcc::Low, size).map_err(|e| format!("{}", e))?; + let mut rgba = Vec::with_capacity(raw.len() * 4); + for b in raw { + let v = if b == 0 { 0 } else { 255 }; + rgba.extend_from_slice(&[v, v, v, 255]); + } + Ok(ImageData { + width: size, + height: size, + bytes: Cow::Owned(rgba), + }) +} + +fn qr_to_clipboard(text: &str) -> Result<(), String> { + let img = qr_image_data(text, 256)?; + let mut clipboard = + ImageClipboard::new().map_err(|e| format!("Failed to initialize clipboard: {}", e))?; + clipboard + .set_image(img) + .map_err(|e| format!("Failed to set clipboard image: {}", e)) +} + static FAILED_TEMP_FILE: LazyLock = LazyLock::new(|| temp_asset!("../assets/failed.mp3")); @@ -1540,6 +1591,16 @@ async fn main() -> Result<(), Box> { })) .build().unwrap(), + ChatCompletionFunctionsArgs::default() + .name("qr_to_clipboard") + .description("Copies a QR code image for the given text to the clipboard.") + .parameters(json!({ + "type": "object", + "properties": { "text": { "type": "string" } }, + "required": ["text"], + })) + .build().unwrap(), + ChatCompletionFunctionsArgs::default() .name("set_speech_speed") .description("Sets how fast the AI voice speaks. Speed must be between 0.5 and 100.0.")