Rework clipboard, fixes writeText() not working on KDE

This commit is contained in:
Exidex 2024-10-20 14:43:46 +02:00
parent b9eea0d5ef
commit 7bd8a5f583
No known key found for this signature in database
GPG key ID: 46D8D21671EB48FA
3 changed files with 189 additions and 104 deletions

View file

@ -1,52 +1,34 @@
use std::cell::RefCell;
use std::io::Cursor;
use std::rc::Rc;
use anyhow::anyhow;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use anyhow::{anyhow, Context, Error};
use arboard::ImageData;
use deno_core::{op, OpState};
use image::RgbaImage;
use serde::{Deserialize, Serialize};
use tokio::task::spawn_blocking;
use crate::plugins::js::permissions::PluginPermissionsClipboard;
use crate::plugins::js::PluginData;
use crate::plugins::js::{clipboard, PluginData};
fn unknown_err_clipboard(err: arboard::Error) -> anyhow::Error {
anyhow!("UNKNOWN_ERROR: {:?}", err)
#[derive(Clone)]
pub struct Clipboard {
clipboard: Arc<RwLock<arboard::Clipboard>>,
}
fn unknown_err_image(err: image::ImageError) -> anyhow::Error {
anyhow!("UNKNOWN_ERROR: {:?}", err)
}
impl Clipboard {
pub fn new() -> anyhow::Result<Self> {
let clipboard = arboard::Clipboard::new()
.context("error while creating clipboard")?;
fn unable_to_convert_image_err() -> anyhow::Error {
anyhow!("UNABLE_TO_CONVERT_IMAGE")
}
#[derive(Debug, Serialize, Deserialize)]
struct ClipboardData {
text_data: Option<String>,
png_data: Option<Vec<u8>>
}
#[op]
async fn clipboard_read(state: Rc<RefCell<OpState>>) -> anyhow::Result<ClipboardData> {
{
let state = state.borrow();
let allow = state
.borrow::<PluginData>()
.permissions()
.clipboard
.contains(&PluginPermissionsClipboard::Read);
if !allow {
return Err(anyhow!("Plugin doesn't have 'read' permission for clipboard"));
}
Ok(Self {
clipboard: Arc::new(RwLock::new(clipboard)),
})
}
spawn_blocking(|| {
let mut clipboard = arboard::Clipboard::new()
.map_err(|err| unknown_err_clipboard(err))?;
fn read(&self) -> anyhow::Result<ClipboardData> {
let mut clipboard = self.clipboard.write().expect("lock is poisoned");
let png_data = match clipboard.get_image() {
Ok(data) => {
@ -86,29 +68,10 @@ async fn clipboard_read(state: Rc<RefCell<OpState>>) -> anyhow::Result<Clipboard
text_data,
png_data,
})
}).await?
}
#[op]
async fn clipboard_read_text(state: Rc<RefCell<OpState>>) -> anyhow::Result<Option<String>> {
{
let state = state.borrow();
let allow = state
.borrow::<PluginData>()
.permissions()
.clipboard
.contains(&PluginPermissionsClipboard::Read);
if !allow {
return Err(anyhow!("Plugin doesn't have 'read' permission for clipboard"));
}
}
spawn_blocking(|| {
let mut clipboard = arboard::Clipboard::new()
.map_err(|err| unknown_err_clipboard(err))?;
fn read_text(&self) -> anyhow::Result<Option<String>> {
let mut clipboard = self.clipboard.write().expect("lock is poisoned");
let data = match clipboard.get_text() {
Ok(data) => Some(data),
@ -123,28 +86,10 @@ async fn clipboard_read_text(state: Rc<RefCell<OpState>>) -> anyhow::Result<Opti
};
Ok(data)
}).await?
}
#[op]
async fn clipboard_write(state: Rc<RefCell<OpState>>, data: ClipboardData) -> anyhow::Result<()> { // TODO deserialization broken, fix when migrating to deno's op2
{
let state = state.borrow();
let allow = state
.borrow::<PluginData>()
.permissions()
.clipboard
.contains(&PluginPermissionsClipboard::Write);
if !allow {
return Err(anyhow!("Plugin doesn't have 'write' permission for clipboard"));
}
}
spawn_blocking(|| {
let mut clipboard = arboard::Clipboard::new()
.map_err(|err| unknown_err_clipboard(err))?;
fn write(&self, data: ClipboardData) -> anyhow::Result<()> {
let mut clipboard = self.clipboard.write().expect("lock is poisoned");
if let Some(png_data) = data.png_data {
@ -175,16 +120,113 @@ async fn clipboard_write(state: Rc<RefCell<OpState>>, data: ClipboardData) -> an
}
Ok(())
}).await?
}
fn write_text(&self, data: String) -> anyhow::Result<()> {
let mut clipboard = self.clipboard.write().expect("lock is poisoned");
clipboard.set_text(data)
.map_err(|err| unknown_err_clipboard(err))?;
Ok(())
}
fn clear(&self) -> anyhow::Result<()> {
let mut clipboard = self.clipboard.write().expect("lock is poisoned");
clipboard.clear()
.map_err(|err| unknown_err_clipboard(err))?;
Ok(())
}
}
fn unknown_err_clipboard(err: arboard::Error) -> Error {
anyhow!("UNKNOWN_ERROR: {:?}", err)
}
fn unknown_err_image(err: image::ImageError) -> Error {
anyhow!("UNKNOWN_ERROR: {:?}", err)
}
fn unable_to_convert_image_err() -> Error {
anyhow!("UNABLE_TO_CONVERT_IMAGE")
}
#[derive(Debug, Serialize, Deserialize)]
struct ClipboardData {
text_data: Option<String>,
png_data: Option<Vec<u8>>
}
#[op]
async fn clipboard_write_text(state: Rc<RefCell<OpState>>, data: String) -> anyhow::Result<()> {
{
async fn clipboard_read(state: Rc<RefCell<OpState>>) -> anyhow::Result<ClipboardData> {
let clipboard = {
let state = state.borrow();
let allow = state
.borrow::<PluginData>()
let plugin_data = state
.borrow::<PluginData>();
let allow = plugin_data
.permissions()
.clipboard
.contains(&PluginPermissionsClipboard::Read);
if !allow {
return Err(anyhow!("Plugin doesn't have 'read' permission for clipboard"));
}
tracing::debug!("Reading from clipboard, plugin id: {:?}", plugin_data.plugin_id);
let clipboard = state
.borrow::<Clipboard>()
.clone();
clipboard
};
spawn_blocking(move || clipboard.read()).await?
}
#[op]
async fn clipboard_read_text(state: Rc<RefCell<OpState>>) -> anyhow::Result<Option<String>> {
let clipboard = {
let state = state.borrow();
let plugin_data = state
.borrow::<PluginData>();
let allow = plugin_data
.permissions()
.clipboard
.contains(&PluginPermissionsClipboard::Read);
if !allow {
return Err(anyhow!("Plugin doesn't have 'read' permission for clipboard"));
}
tracing::debug!("Reading text from clipboard, plugin id: {:?}", plugin_data.plugin_id);
let clipboard = state
.borrow::<Clipboard>()
.clone();
clipboard
};
spawn_blocking(move || clipboard.read_text()).await?
}
#[op]
async fn clipboard_write(state: Rc<RefCell<OpState>>, data: ClipboardData) -> anyhow::Result<()> { // TODO deserialization broken, fix when migrating to deno's op2
let clipboard = {
let state = state.borrow();
let plugin_data = state
.borrow::<PluginData>();
let allow = plugin_data
.permissions()
.clipboard
.contains(&PluginPermissionsClipboard::Write);
@ -192,26 +234,57 @@ async fn clipboard_write_text(state: Rc<RefCell<OpState>>, data: String) -> anyh
if !allow {
return Err(anyhow!("Plugin doesn't have 'write' permission for clipboard"));
}
}
spawn_blocking(|| {
let mut clipboard = arboard::Clipboard::new()
.map_err(|err| unknown_err_clipboard(err))?;
tracing::debug!("Writing to clipboard, plugin id: {:?}", plugin_data.plugin_id);
clipboard.set_text(data)
.map_err(|err| unknown_err_clipboard(err))?;
let clipboard = state
.borrow::<Clipboard>()
.clone();
Ok(())
}).await?
clipboard
};
spawn_blocking(move || clipboard.write(data)).await?
}
#[op]
async fn clipboard_write_text(state: Rc<RefCell<OpState>>, data: String) -> anyhow::Result<()> {
let clipboard = {
let state = state.borrow();
let plugin_data = state
.borrow::<PluginData>();
let allow = plugin_data
.permissions()
.clipboard
.contains(&PluginPermissionsClipboard::Write);
if !allow {
return Err(anyhow!("Plugin doesn't have 'write' permission for clipboard"));
}
tracing::debug!("Writing text to clipboard, plugin id: {:?}", plugin_data.plugin_id);
let clipboard = state
.borrow::<Clipboard>()
.clone();
clipboard
};
spawn_blocking(move || clipboard.write_text(data)).await?
}
#[op]
async fn clipboard_clear(state: Rc<RefCell<OpState>>) -> anyhow::Result<()> {
{
let clipboard = {
let state = state.borrow();
let allow = state
.borrow::<PluginData>()
let plugin_data = state
.borrow::<PluginData>();
let allow = plugin_data
.permissions()
.clipboard
.contains(&PluginPermissionsClipboard::Clear);
@ -219,14 +292,15 @@ async fn clipboard_clear(state: Rc<RefCell<OpState>>) -> anyhow::Result<()> {
if !allow {
return Err(anyhow!("Plugin doesn't have 'clear' permission for clipboard"));
}
}
spawn_blocking(|| {
let mut clipboard = arboard::Clipboard::new()
.map_err(|err| unknown_err_clipboard(err))?;
tracing::debug!("Clearing clipboard, plugin id: {:?}", plugin_data.plugin_id);
clipboard.clear()?;
let clipboard = state
.borrow::<Clipboard>()
.clone();
Ok(())
}).await?
clipboard
};
spawn_blocking(move || clipboard.clear()).await?
}

View file

@ -37,7 +37,7 @@ use crate::plugins::applications::{get_apps, DesktopEntry};
use crate::plugins::data_db_repository::{db_entrypoint_from_str, DataDbRepository, DbPluginClipboardPermissions, DbPluginEntrypointType, DbPluginPreference, DbPluginPreferenceUserData, DbReadPlugin, DbReadPluginEntrypoint};
use crate::plugins::icon_cache::IconCache;
use crate::plugins::js::assets::{asset_data, asset_data_blocking};
use crate::plugins::js::clipboard::{clipboard_clear, clipboard_read, clipboard_read_text, clipboard_write, clipboard_write_text};
use crate::plugins::js::clipboard::{clipboard_clear, clipboard_read, clipboard_read_text, clipboard_write, clipboard_write_text, Clipboard};
use crate::plugins::js::command_generators::get_command_generator_entrypoint_ids;
use crate::plugins::js::logs::{op_log_debug, op_log_error, op_log_info, op_log_trace, op_log_warn};
use crate::plugins::js::permissions::{permissions_to_deno, PluginPermissions, PluginPermissionsClipboard};
@ -57,7 +57,7 @@ mod assets;
mod preferences;
mod search;
mod command_generators;
mod clipboard;
pub mod clipboard;
pub mod permissions;
pub struct PluginRuntimeData {
@ -74,6 +74,7 @@ pub struct PluginRuntimeData {
pub icon_cache: IconCache,
pub frontend_api: FrontendApi,
pub dirs: Dirs,
pub clipboard: Clipboard,
}
pub struct PluginCode {
@ -252,7 +253,8 @@ pub async fn start_plugin_runtime(data: PluginRuntimeData, run_status_guard: Run
data.db_repository,
data.search_index,
data.icon_cache,
data.dirs
data.dirs,
data.clipboard
).await
})
} => {
@ -294,6 +296,7 @@ async fn start_js_runtime(
search_index: SearchIndex,
icon_cache: IconCache,
dirs: Dirs,
clipboard: Clipboard,
) -> anyhow::Result<()> {
let dev_plugin = plugin_id.to_string().starts_with("file://");
@ -353,7 +356,8 @@ async fn start_js_runtime(
repository,
search_index,
icon_cache,
numbat_context
numbat_context,
clipboard,
)],
// maybe_inspector_server: Some(inspector_server.clone()),
// should_wait_for_inspector_session: true,
@ -543,6 +547,7 @@ deno_core::extension!(
search_index: SearchIndex,
icon_cache: IconCache,
numbat_context: Option<NumbatContext>,
clipboard: Clipboard,
},
state = |state, options| {
state.put(options.event_receiver);
@ -553,6 +558,7 @@ deno_core::extension!(
state.put(options.search_index);
state.put(options.icon_cache);
state.put(options.numbat_context);
state.put(options.clipboard);
},
);

View file

@ -21,6 +21,7 @@ use crate::plugins::data_db_repository::{DataDbRepository, db_entrypoint_from_st
use crate::plugins::global_shortcut::{convert_physical_shortcut_to_hotkey, register_listener};
use crate::plugins::icon_cache::IconCache;
use crate::plugins::js::{AllPluginCommandData, OnePluginCommandData, PluginCode, PluginCommand, PluginRuntimeData, start_plugin_runtime};
use crate::plugins::js::clipboard::Clipboard;
use crate::plugins::js::permissions::{PluginPermissions, PluginPermissionsClipboard, PluginPermissionsExec, PluginPermissionsFileSystem, PluginPermissionsMainSearchBar};
use crate::plugins::loader::PluginLoader;
use crate::plugins::run_status::RunStatusHolder;
@ -53,7 +54,8 @@ pub struct ApplicationManager {
frontend_api: FrontendApi,
global_hotkey_manager: GlobalHotKeyManager,
current_hotkey: Mutex<Option<HotKey>>,
dirs: Dirs
dirs: Dirs,
clipboard: Clipboard,
}
impl ApplicationManager {
@ -67,6 +69,7 @@ impl ApplicationManager {
let run_status_holder = RunStatusHolder::new();
let search_index = SearchIndex::create_index(frontend_api.clone())?;
let global_hotkey_manager = GlobalHotKeyManager::new()?;
let clipboard = Clipboard::new()?;
let (command_broadcaster, _) = tokio::sync::broadcast::channel::<PluginCommand>(100);
@ -82,6 +85,7 @@ impl ApplicationManager {
icon_cache,
frontend_api,
global_hotkey_manager,
clipboard,
current_hotkey: Mutex::new(None),
dirs
};
@ -564,7 +568,8 @@ impl ApplicationManager {
search_index: self.search_index.clone(),
icon_cache: self.icon_cache.clone(),
frontend_api: self.frontend_api.clone(),
dirs: self.dirs.clone()
dirs: self.dirs.clone(),
clipboard: self.clipboard.clone(),
};
self.start_plugin_runtime(data);