mirror of
https://github.com/project-gauntlet/gauntlet.git
synced 2025-12-23 10:35:53 +00:00
Rework clipboard, fixes writeText() not working on KDE
This commit is contained in:
parent
b9eea0d5ef
commit
7bd8a5f583
3 changed files with 189 additions and 104 deletions
|
|
@ -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?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue