diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index 309395c..f83e26b 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -31,6 +31,9 @@ #include #include +#pragma comment( lib, "gdiplus.lib" ) +#include + // How many milliseconds must pass between keystrokes to refresh the keyboard layout const long refreshKeyboardLayoutInterval = 2000; @@ -656,3 +659,43 @@ int32_t get_clipboard(wchar_t *buffer, int32_t size) { CloseClipboard(); } + +int32_t set_clipboard_image(wchar_t *path) { + bool result = false; + + Gdiplus::GdiplusStartupInput gdiplusStartupInput; + ULONG_PTR gdiplusToken; + Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); + + Gdiplus::Bitmap *gdibmp = Gdiplus::Bitmap::FromFile(path); + if (gdibmp) + { + HBITMAP hbitmap; + gdibmp->GetHBITMAP(0, &hbitmap); + if (OpenClipboard(NULL)) + { + EmptyClipboard(); + DIBSECTION ds; + if (GetObject(hbitmap, sizeof(DIBSECTION), &ds)) + { + HDC hdc = GetDC(HWND_DESKTOP); + //create compatible bitmap (get DDB from DIB) + HBITMAP hbitmap_ddb = CreateDIBitmap(hdc, &ds.dsBmih, CBM_INIT, + ds.dsBm.bmBits, (BITMAPINFO*)&ds.dsBmih, DIB_RGB_COLORS); + ReleaseDC(HWND_DESKTOP, hdc); + SetClipboardData(CF_BITMAP, hbitmap_ddb); + DeleteObject(hbitmap_ddb); + result = true; + } + CloseClipboard(); + } + + //cleanup: + DeleteObject(hbitmap); + delete gdibmp; + } + + Gdiplus::GdiplusShutdown(gdiplusToken); + + return result; +} diff --git a/native/libwinbridge/bridge.h b/native/libwinbridge/bridge.h index 85d86ad..77fa4d4 100644 --- a/native/libwinbridge/bridge.h +++ b/native/libwinbridge/bridge.h @@ -146,4 +146,9 @@ extern "C" int32_t get_clipboard(wchar_t * buffer, int32_t size); */ extern "C" int32_t set_clipboard(wchar_t * text); +/* + * Set the clipboard image to the given path + */ +extern "C" int32_t set_clipboard_image(wchar_t * path); + #endif //ESPANSO_BRIDGE_H \ No newline at end of file diff --git a/src/bridge/windows.rs b/src/bridge/windows.rs index db9833e..ff916fa 100644 --- a/src/bridge/windows.rs +++ b/src/bridge/windows.rs @@ -47,6 +47,7 @@ extern { // CLIPBOARD pub fn get_clipboard(buffer: *mut u16, size: i32) -> i32; pub fn set_clipboard(payload: *const u16) -> i32; + pub fn set_clipboard_image(path: *const u16) -> i32; // KEYBOARD pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u16, diff --git a/src/clipboard/windows.rs b/src/clipboard/windows.rs index caa8e0e..63fa070 100644 --- a/src/clipboard/windows.rs +++ b/src/clipboard/windows.rs @@ -18,7 +18,8 @@ */ use widestring::U16CString; -use crate::bridge::windows::{set_clipboard, get_clipboard}; +use crate::bridge::windows::{set_clipboard, get_clipboard, set_clipboard_image}; +use std::path::Path; pub struct WindowsClipboardManager { @@ -53,4 +54,12 @@ impl super::ClipboardManager for WindowsClipboardManager { set_clipboard(payload_c.as_ptr()); } } + + fn set_clipboard_image(&self, image_path: &Path) { + let path_string = image_path.to_string_lossy().into_owned(); + unsafe { + let payload_c = U16CString::from_str(path_string).unwrap(); + set_clipboard_image(payload_c.as_ptr()); + } + } } \ No newline at end of file diff --git a/src/engine.rs b/src/engine.rs index d0163e6..19f96f8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -218,14 +218,12 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa // Image Match MatchContentType::Image(content) => { - let image_path = PathBuf::from(&content.path); - // Make sure the image exist beforehand - if image_path.exists() { - self.clipboard_manager.set_clipboard_image(&image_path); + if content.path.exists() { + self.clipboard_manager.set_clipboard_image(&content.path); self.keyboard_manager.trigger_paste(); }else{ - error!("Image not found in path: {:?}", image_path); + error!("Image not found in path: {:?}", content.path); } }, } diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index 2811a07..947dce0 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -22,6 +22,8 @@ use crate::event::{KeyEvent, KeyModifier}; use crate::event::KeyEventReceiver; use serde_yaml::Mapping; use regex::Regex; +use std::path::PathBuf; +use std::fs; pub(crate) mod scrolling; @@ -53,7 +55,7 @@ pub struct TextContent { #[derive(Debug, Serialize, Clone)] pub struct ImageContent { - pub path: String, + pub path: PathBuf, } impl <'de> serde::Deserialize<'de> for Match { @@ -97,8 +99,29 @@ impl<'a> From<&'a AutoMatch> for Match{ MatchContentType::Text(content) }else if let Some(image_path) = &other.image_path { // Image match + // On Windows, we have to replace the forward / with the backslash \ in the path + let new_path = if cfg!(target_os = "windows") { + image_path.replace("/", "\\") + }else{ + image_path.to_owned() + }; + + // Calculate variables in path + let new_path = if new_path.contains("$CONFIG") { + let config_dir = crate::context::get_config_dir(); + let config_path = fs::canonicalize(&config_dir); + let config_path = if let Ok(config_path) = config_path { + config_path.to_string_lossy().into_owned() + }else{ + "".to_owned() + }; + new_path.replace("$CONFIG", &config_path) + }else{ + new_path.to_owned() + }; + let content = ImageContent { - path: image_path.clone() + path: PathBuf::from(new_path) }; MatchContentType::Image(content)