[WIP] Encryption for secure values (#183)

This commit is contained in:
Gregory Schier
2025-04-15 07:18:26 -07:00
committed by GitHub
parent e114a85c39
commit 2e55a1bd6d
208 changed files with 4063 additions and 28698 deletions

40
src-tauri/src/commands.rs Normal file
View File

@@ -0,0 +1,40 @@
use crate::error::Result;
use tauri::{command, AppHandle, Manager, Runtime, WebviewWindow};
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
use yaak_crypto::manager::EncryptionManagerExt;
use yaak_plugins::events::PluginWindowContext;
use yaak_plugins::native_template_functions::{decrypt_secure_template_function, encrypt_secure_template_function};
#[command]
pub(crate) async fn cmd_show_workspace_key<R: Runtime>(
window: WebviewWindow<R>,
workspace_id: &str,
) -> Result<()> {
let key = window.crypto().reveal_workspace_key(workspace_id)?;
window
.dialog()
.message(format!("Your workspace key is \n\n{}", key))
.kind(MessageDialogKind::Info)
.show(|_v| {});
Ok(())
}
#[command]
pub(crate) async fn cmd_decrypt_template<R: Runtime>(
window: WebviewWindow<R>,
template: &str,
) -> Result<String> {
let app_handle = window.app_handle();
let window_context = &PluginWindowContext::new(&window);
Ok(decrypt_secure_template_function(&app_handle, window_context, template)?)
}
#[command]
pub(crate) async fn cmd_secure_template<R: Runtime>(
app_handle: AppHandle<R>,
window: WebviewWindow<R>,
template: &str,
) -> Result<String> {
let window_context = &PluginWindowContext::new(&window);
Ok(encrypt_secure_template_function(&app_handle, window_context, template)?)
}

View File

@@ -1,3 +1,4 @@
use std::io;
use serde::{Serialize, Serializer};
use thiserror::Error;
@@ -11,6 +12,9 @@ pub enum Error {
#[error(transparent)]
SyncError(#[from] yaak_sync::error::Error),
#[error(transparent)]
CryptoError(#[from] yaak_crypto::error::Error),
#[error(transparent)]
GitError(#[from] yaak_git::error::Error),
@@ -26,6 +30,18 @@ pub enum Error {
#[error("Updater error: {0}")]
UpdaterError(#[from] tauri_plugin_updater::Error),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::error::Error),
#[error("Tauri error: {0}")]
TauriError(#[from] tauri::Error),
#[error("Event source error: {0}")]
EventSourceError(#[from] eventsource_client::Error),
#[error("I/O error: {0}")]
IOError(#[from] io::Error),
#[error("Request error: {0}")]
RequestError(#[from] reqwest::Error),

View File

@@ -31,7 +31,7 @@ use yaak_models::models::{
use yaak_models::query_manager::QueryManagerExt;
use yaak_models::util::UpdateSource;
use yaak_plugins::events::{
CallHttpAuthenticationRequest, HttpHeader, RenderPurpose, WindowContext,
CallHttpAuthenticationRequest, HttpHeader, PluginWindowContext, RenderPurpose,
};
use yaak_plugins::manager::PluginManager;
use yaak_plugins::template_callback::PluginTemplateCallback;
@@ -60,7 +60,7 @@ pub async fn send_http_request<R: Runtime>(
let cb = PluginTemplateCallback::new(
window.app_handle(),
&WindowContext::from_window(window),
&PluginWindowContext::new(window),
RenderPurpose::Send,
);
let update_source = UpdateSource::from_window(window);
@@ -383,7 +383,7 @@ pub async fn send_http_request<R: Runtime>(
};
}
// Set file path if not empty
// Set file path if it is not empty
if !file_path.is_empty() {
let filename = PathBuf::from(file_path)
.file_name()
@@ -442,7 +442,7 @@ pub async fn send_http_request<R: Runtime>(
})
.collect(),
};
let auth_result = plugin_manager.call_http_authentication(window, &auth_name, req).await;
let auth_result = plugin_manager.call_http_authentication(&window, &auth_name, req).await;
let plugin_result = match auth_result {
Ok(r) => r,
Err(e) => {

View File

@@ -1,6 +1,4 @@
extern crate core;
#[cfg(target_os = "macos")]
extern crate objc;
use crate::encoding::read_response_body;
use crate::error::Error::GenericError;
use crate::grpc::metadata_to_map;
@@ -43,14 +41,15 @@ use yaak_plugins::events::{
BootResponse, CallHttpAuthenticationRequest, CallHttpRequestActionRequest, FilterResponse,
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, HttpHeader, InternalEvent,
InternalEventPayload, JsonPrimitive, RenderPurpose, WindowContext,
InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose,
};
use yaak_plugins::manager::PluginManager;
use yaak_plugins::template_callback::PluginTemplateCallback;
use yaak_sse::sse::ServerSentEvent;
use yaak_templates::format::format_json;
use yaak_templates::{Parser, Tokens};
use yaak_templates::{Tokens, transform_args};
mod commands;
mod encoding;
mod error;
mod grpc;
@@ -59,8 +58,6 @@ mod http_request;
mod notifications;
mod plugin_events;
mod render;
#[cfg(target_os = "macos")]
mod tauri_plugin_mac_window;
mod updates;
mod uri_scheme;
mod window;
@@ -77,9 +74,9 @@ struct AppMetaData {
}
#[tauri::command]
async fn cmd_metadata(app_handle: AppHandle) -> Result<AppMetaData, ()> {
let app_data_dir = app_handle.path().app_data_dir().unwrap();
let app_log_dir = app_handle.path().app_log_dir().unwrap();
async fn cmd_metadata(app_handle: AppHandle) -> YaakResult<AppMetaData> {
let app_data_dir = app_handle.path().app_data_dir()?;
let app_log_dir = app_handle.path().app_log_dir()?;
Ok(AppMetaData {
is_dev: is_dev(),
version: app_handle.package_info().version.to_string(),
@@ -90,13 +87,18 @@ async fn cmd_metadata(app_handle: AppHandle) -> Result<AppMetaData, ()> {
}
#[tauri::command]
async fn cmd_parse_template(template: &str) -> YaakResult<Tokens> {
Ok(Parser::new(template).parse()?)
}
#[tauri::command]
async fn cmd_template_tokens_to_string(tokens: Tokens) -> Result<String, String> {
Ok(tokens.to_string())
async fn cmd_template_tokens_to_string<R: Runtime>(
window: WebviewWindow<R>,
app_handle: AppHandle<R>,
tokens: Tokens,
) -> YaakResult<String> {
let cb = PluginTemplateCallback::new(
&app_handle,
&PluginWindowContext::new(&window),
RenderPurpose::Preview,
);
let new_tokens = transform_args(tokens, &cb)?;
Ok(new_tokens.to_string())
}
#[tauri::command]
@@ -118,7 +120,7 @@ async fn cmd_render_template<R: Runtime>(
environment.as_ref(),
&PluginTemplateCallback::new(
&app_handle,
&WindowContext::from_window(&window),
&PluginWindowContext::new(&window),
RenderPurpose::Preview,
),
)
@@ -131,8 +133,8 @@ async fn cmd_dismiss_notification<R: Runtime>(
window: WebviewWindow<R>,
notification_id: &str,
yaak_notifier: State<'_, Mutex<YaakNotifier>>,
) -> Result<(), String> {
yaak_notifier.lock().await.seen(&window, notification_id).await
) -> YaakResult<()> {
Ok(yaak_notifier.lock().await.seen(&window, notification_id).await?)
}
#[tauri::command]
@@ -157,11 +159,11 @@ async fn cmd_grpc_reflect<R: Runtime>(
environment.as_ref(),
&PluginTemplateCallback::new(
&app_handle,
&WindowContext::from_window(&window),
&PluginWindowContext::new(&window),
RenderPurpose::Send,
),
)
.await?;
.await?;
let uri = safe_uri(&req.url);
@@ -200,7 +202,7 @@ async fn cmd_grpc_go<R: Runtime>(
environment.as_ref(),
&PluginTemplateCallback::new(
&app_handle,
&WindowContext::from_window(&window),
&PluginWindowContext::new(&window),
RenderPurpose::Send,
),
)
@@ -356,7 +358,7 @@ async fn cmd_grpc_go<R: Runtime>(
environment.as_ref(),
&PluginTemplateCallback::new(
&app_handle,
&WindowContext::from_window(&window),
&PluginWindowContext::new(&window),
RenderPurpose::Send,
),
)
@@ -425,7 +427,7 @@ async fn cmd_grpc_go<R: Runtime>(
environment.as_ref(),
&PluginTemplateCallback::new(
&app_handle,
&WindowContext::from_window(&window),
&PluginWindowContext::new(&window),
RenderPurpose::Send,
),
)
@@ -742,7 +744,7 @@ async fn cmd_send_ephemeral_request<R: Runtime>(
}
#[tauri::command]
async fn cmd_format_json(text: &str) -> Result<String, String> {
async fn cmd_format_json(text: &str) -> YaakResult<String> {
Ok(format_json(text, " "))
}
@@ -777,10 +779,10 @@ async fn cmd_filter_response<R: Runtime>(
}
#[tauri::command]
async fn cmd_get_sse_events(file_path: &str) -> Result<Vec<ServerSentEvent>, String> {
let body = fs::read(file_path).map_err(|e| e.to_string())?;
async fn cmd_get_sse_events(file_path: &str) -> YaakResult<Vec<ServerSentEvent>> {
let body = fs::read(file_path)?;
let mut event_parser = EventParser::new();
event_parser.process_bytes(body.into()).map_err(|e| e.to_string())?;
event_parser.process_bytes(body.into())?;
let mut events = Vec::new();
while let Some(e) = event_parser.get_event() {
@@ -897,27 +899,24 @@ async fn cmd_import_data<R: Runtime>(
async fn cmd_http_request_actions<R: Runtime>(
window: WebviewWindow<R>,
plugin_manager: State<'_, PluginManager>,
) -> Result<Vec<GetHttpRequestActionsResponse>, String> {
plugin_manager.get_http_request_actions(&window).await.map_err(|e| e.to_string())
) -> YaakResult<Vec<GetHttpRequestActionsResponse>> {
Ok(plugin_manager.get_http_request_actions(&window).await?)
}
#[tauri::command]
async fn cmd_template_functions<R: Runtime>(
window: WebviewWindow<R>,
plugin_manager: State<'_, PluginManager>,
) -> Result<Vec<GetTemplateFunctionsResponse>, String> {
plugin_manager.get_template_functions(&window).await.map_err(|e| e.to_string())
) -> YaakResult<Vec<GetTemplateFunctionsResponse>> {
Ok(plugin_manager.get_template_functions(&window).await?)
}
#[tauri::command]
async fn cmd_get_http_authentication_summaries<R: Runtime>(
window: WebviewWindow<R>,
plugin_manager: State<'_, PluginManager>,
) -> Result<Vec<GetHttpAuthenticationSummaryResponse>, String> {
let results = plugin_manager
.get_http_authentication_summaries(&window)
.await
.map_err(|e| e.to_string())?;
) -> YaakResult<Vec<GetHttpAuthenticationSummaryResponse>> {
let results = plugin_manager.get_http_authentication_summaries(&window).await?;
Ok(results.into_iter().map(|(_, a)| a).collect())
}
@@ -928,11 +927,10 @@ async fn cmd_get_http_authentication_config<R: Runtime>(
auth_name: &str,
values: HashMap<String, JsonPrimitive>,
request_id: &str,
) -> Result<GetHttpAuthenticationConfigResponse, String> {
plugin_manager
) -> YaakResult<GetHttpAuthenticationConfigResponse> {
Ok(plugin_manager
.get_http_authentication_config(&window, auth_name, values, request_id)
.await
.map_err(|e| e.to_string())
.await?)
}
#[tauri::command]
@@ -940,8 +938,8 @@ async fn cmd_call_http_request_action<R: Runtime>(
window: WebviewWindow<R>,
req: CallHttpRequestActionRequest,
plugin_manager: State<'_, PluginManager>,
) -> Result<(), String> {
plugin_manager.call_http_request_action(&window, req).await.map_err(|e| e.to_string())
) -> YaakResult<()> {
Ok(plugin_manager.call_http_request_action(&window, req).await?)
}
#[tauri::command]
@@ -952,11 +950,10 @@ async fn cmd_call_http_authentication_action<R: Runtime>(
action_index: i32,
values: HashMap<String, JsonPrimitive>,
request_id: &str,
) -> Result<(), String> {
plugin_manager
) -> YaakResult<()> {
Ok(plugin_manager
.call_http_authentication_action(&window, auth_name, action_index, values, request_id)
.await
.map_err(|e| e.to_string())
.await?)
}
#[tauri::command]
@@ -965,18 +962,20 @@ async fn cmd_curl_to_request<R: Runtime>(
command: &str,
plugin_manager: State<'_, PluginManager>,
workspace_id: &str,
) -> Result<HttpRequest, String> {
let import_result =
{ plugin_manager.import_data(&window, command).await.map_err(|e| e.to_string())? };
) -> YaakResult<HttpRequest> {
let import_result = plugin_manager.import_data(&window, command).await?;
import_result.resources.http_requests.get(0).ok_or("No curl command found".to_string()).map(
|r| {
Ok(import_result
.resources
.http_requests
.get(0)
.ok_or(GenericError("No curl command found".to_string()))
.map(|r| {
let mut request = r.clone();
request.workspace_id = workspace_id.into();
request.id = "".to_string();
request
},
)
})?)
}
#[tauri::command]
@@ -985,11 +984,9 @@ async fn cmd_export_data<R: Runtime>(
export_path: &str,
workspace_ids: Vec<&str>,
include_environments: bool,
) -> Result<(), String> {
) -> YaakResult<()> {
let export_data =
get_workspace_export_resources(&app_handle, workspace_ids, include_environments)
.await
.map_err(|e| e.to_string())?;
get_workspace_export_resources(&app_handle, workspace_ids, include_environments).await?;
let f = File::options()
.create(true)
.truncate(true)
@@ -998,7 +995,7 @@ async fn cmd_export_data<R: Runtime>(
.expect("Unable to create file");
serde_json::to_writer_pretty(&f, &export_data)
.map_err(|e| e.to_string())
.map_err(|e| GenericError(e.to_string()))
.expect("Failed to write");
f.sync_all().expect("Failed to sync");
@@ -1092,9 +1089,7 @@ async fn cmd_install_plugin<R: Runtime>(
app_handle: AppHandle<R>,
window: WebviewWindow<R>,
) -> YaakResult<Plugin> {
plugin_manager
.add_plugin_by_dir(&WindowContext::from_window(&window), &directory, true)
.await?;
plugin_manager.add_plugin_by_dir(&PluginWindowContext::new(&window), &directory, true).await?;
Ok(app_handle.db().upsert_plugin(
&Plugin {
@@ -1116,9 +1111,7 @@ async fn cmd_uninstall_plugin<R: Runtime>(
let plugin =
app_handle.db().delete_plugin_by_id(plugin_id, &UpdateSource::from_window(&window))?;
plugin_manager
.uninstall(&WindowContext::from_window(&window), plugin.directory.as_str())
.await?;
plugin_manager.uninstall(&PluginWindowContext::new(&window), plugin.directory.as_str()).await?;
Ok(plugin)
}
@@ -1149,11 +1142,8 @@ async fn cmd_reload_plugins<R: Runtime>(
app_handle: AppHandle<R>,
window: WebviewWindow<R>,
plugin_manager: State<'_, PluginManager>,
) -> Result<(), String> {
plugin_manager
.initialize_all_plugins(&app_handle, &WindowContext::from_window(&window))
.await
.map_err(|e| e.to_string())?;
) -> YaakResult<()> {
plugin_manager.initialize_all_plugins(&app_handle, &PluginWindowContext::new(&window)).await?;
Ok(())
}
@@ -1212,12 +1202,11 @@ async fn cmd_delete_all_http_responses<R: Runtime>(
#[tauri::command]
async fn cmd_get_workspace_meta<R: Runtime>(
app_handle: AppHandle<R>,
window: WebviewWindow<R>,
workspace_id: &str,
) -> YaakResult<WorkspaceMeta> {
let db = app_handle.db();
let workspace = db.get_workspace(workspace_id)?;
Ok(db.get_or_create_workspace_meta(&workspace.id, &UpdateSource::from_window(&window))?)
Ok(db.get_or_create_workspace_meta(&workspace.id)?)
}
#[tauri::command]
@@ -1227,13 +1216,13 @@ async fn cmd_new_child_window(
label: &str,
title: &str,
inner_size: (f64, f64),
) -> Result<(), String> {
) -> YaakResult<()> {
window::create_child_window(&parent_window, url, label, title, inner_size);
Ok(())
}
#[tauri::command]
async fn cmd_new_main_window(app_handle: AppHandle, url: &str) -> Result<(), String> {
async fn cmd_new_main_window(app_handle: AppHandle, url: &str) -> YaakResult<()> {
window::create_main_window(&app_handle, url);
Ok(())
}
@@ -1295,17 +1284,14 @@ pub fn run() {
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_fs::init())
.plugin(yaak_license::init())
.plugin(yaak_mac_window::init())
.plugin(yaak_models::init())
.plugin(yaak_plugins::init())
.plugin(yaak_crypto::init())
.plugin(yaak_git::init())
.plugin(yaak_ws::init())
.plugin(yaak_sync::init());
#[cfg(target_os = "macos")]
{
builder = builder.plugin(tauri_plugin_mac_window::init());
}
builder
.setup(|app| {
let app_data_dir = app.path().app_data_dir().unwrap();
@@ -1352,7 +1338,6 @@ pub fn run() {
cmd_metadata,
cmd_new_child_window,
cmd_new_main_window,
cmd_parse_template,
cmd_plugin_info,
cmd_reload_plugins,
cmd_render_template,
@@ -1362,6 +1347,12 @@ pub fn run() {
cmd_template_functions,
cmd_template_tokens_to_string,
cmd_uninstall_plugin,
//
//
// Migrated commands
crate::commands::cmd_decrypt_template,
crate::commands::cmd_secure_template,
crate::commands::cmd_show_workspace_key,
])
.register_uri_scheme_protocol("yaak", handle_uri_scheme)
.build(tauri::generate_context!())
@@ -1495,11 +1486,11 @@ async fn call_frontend<R: Runtime>(
fn get_window_from_window_context<R: Runtime>(
app_handle: &AppHandle<R>,
window_context: &WindowContext,
window_context: &PluginWindowContext,
) -> Option<WebviewWindow<R>> {
let label = match window_context {
WindowContext::Label { label } => label,
WindowContext::None => {
PluginWindowContext::Label { label, .. } => label,
PluginWindowContext::None => {
return app_handle.webview_windows().iter().next().map(|(_, w)| w.to_owned());
}
};

View File

@@ -1,5 +1,6 @@
use std::time::SystemTime;
use crate::error::Result;
use crate::history::{get_num_launches, get_os};
use chrono::{DateTime, Duration, Utc};
use log::debug;
@@ -44,16 +45,12 @@ impl YaakNotifier {
}
}
pub async fn seen<R: Runtime>(
&mut self,
window: &WebviewWindow<R>,
id: &str,
) -> Result<(), String> {
pub async fn seen<R: Runtime>(&mut self, window: &WebviewWindow<R>, id: &str) -> Result<()> {
let app_handle = window.app_handle();
let mut seen = get_kv(app_handle).await?;
seen.push(id.to_string());
debug!("Marked notification as seen {}", id);
let seen_json = serde_json::to_string(&seen).map_err(|e| e.to_string())?;
let seen_json = serde_json::to_string(&seen)?;
window.db().set_key_value_raw(
KV_NAMESPACE,
KV_KEY,
@@ -63,7 +60,7 @@ impl YaakNotifier {
Ok(())
}
pub async fn check<R: Runtime>(&mut self, window: &WebviewWindow<R>) -> Result<(), String> {
pub async fn check<R: Runtime>(&mut self, window: &WebviewWindow<R>) -> Result<()> {
let app_handle = window.app_handle();
let ignore_check = self.last_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
@@ -82,13 +79,13 @@ impl YaakNotifier {
("launches", num_launches.to_string().as_str()),
("platform", get_os()),
]);
let resp = req.send().await.map_err(|e| e.to_string())?;
let resp = req.send().await?;
if resp.status() != 200 {
debug!("Skipping notification status code {}", resp.status());
return Ok(());
}
let result = resp.json::<Value>().await.map_err(|e| e.to_string())?;
let result = resp.json::<Value>().await?;
// Support both single and multiple notifications.
// TODO: Remove support for single after April 2025
@@ -117,9 +114,9 @@ impl YaakNotifier {
}
}
async fn get_kv<R: Runtime>(app_handle: &AppHandle<R>) -> Result<Vec<String>, String> {
async fn get_kv<R: Runtime>(app_handle: &AppHandle<R>) -> Result<Vec<String>> {
match app_handle.db().get_key_value_raw("notifications", "seen") {
None => Ok(Vec::new()),
Some(v) => serde_json::from_str(&v.value).map_err(|e| e.to_string()),
Some(v) => Ok(serde_json::from_str(&v.value)?),
}
}

View File

@@ -1,6 +1,6 @@
use crate::http_request::send_http_request;
use crate::render::{render_http_request, render_json_value};
use crate::window::{create_window, CreateWindowConfig};
use crate::window::{CreateWindowConfig, create_window};
use crate::{
call_frontend, cookie_jar_from_window, environment_from_window, get_window_from_window_context,
workspace_from_window,
@@ -15,8 +15,8 @@ use yaak_models::util::UpdateSource;
use yaak_plugins::events::{
Color, DeleteKeyValueResponse, EmptyPayload, FindHttpResponsesResponse,
GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent, InternalEventPayload,
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest,
TemplateRenderResponse, WindowContext, WindowNavigateEvent,
PluginWindowContext, RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse,
ShowToastRequest, TemplateRenderResponse, WindowNavigateEvent,
};
use yaak_plugins::manager::PluginManager;
use yaak_plugins::plugin_handle::PluginHandle;
@@ -39,7 +39,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
}
InternalEventPayload::ShowToastRequest(req) => {
match window_context {
WindowContext::Label { label } => app_handle
PluginWindowContext::Label { label, .. } => app_handle
.emit_to(label, "show_toast", req)
.expect("Failed to emit show_toast to window"),
_ => app_handle.emit("show_toast", req).expect("Failed to emit show_toast"),
@@ -54,17 +54,14 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
InternalEventPayload::FindHttpResponsesRequest(req) => {
let http_responses = app_handle
.db()
.list_http_responses_for_request(
req.request_id.as_str(),
req.limit.map(|l| l as u64),
)
.list_http_responses_for_request(&req.request_id, req.limit.map(|l| l as u64))
.unwrap_or_default();
Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
http_responses,
}))
}
InternalEventPayload::GetHttpRequestByIdRequest(req) => {
let http_request = app_handle.db().get_http_request(req.id.as_str()).ok();
let http_request = app_handle.db().get_http_request(&req.id).ok();
Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
http_request,
}))
@@ -111,10 +108,8 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data }))
}
InternalEventPayload::ErrorResponse(resp) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for plugin reload");
let toast_event = plugin_handle.build_event_to_send(
&WindowContext::from_window(&window),
&window_context,
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
message: format!(
"Plugin error from {}: {}",
@@ -143,7 +138,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin).unwrap();
}
let toast_event = plugin_handle.build_event_to_send(
&event.window_context,
&window_context,
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
message: format!("Reloaded plugin {}", plugin_handle.dir),
icon: Some(Icon::Info),
@@ -221,13 +216,12 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
{
let event_id = event.id.clone();
let plugin_handle = plugin_handle.clone();
let label = label.clone();
let window_context = window_context.clone();
tauri::async_runtime::spawn(async move {
while let Some(url) = navigation_rx.recv().await {
let url = url.to_string();
let label = label.clone();
let event_to_send = plugin_handle.build_event_to_send(
&WindowContext::Label { label },
&window_context, // NOTE: Sending existing context on purpose here
&InternalEventPayload::WindowNavigateEvent(WindowNavigateEvent { url }),
Some(event_id.clone()),
);
@@ -239,12 +233,11 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
{
let event_id = event.id.clone();
let plugin_handle = plugin_handle.clone();
let label = label.clone();
let window_context = window_context.clone();
tauri::async_runtime::spawn(async move {
while let Some(_) = close_rx.recv().await {
let label = label.clone();
let event_to_send = plugin_handle.build_event_to_send(
&WindowContext::Label { label },
&window_context,
&InternalEventPayload::WindowCloseEvent,
Some(event_id.clone()),
);

View File

@@ -1,457 +0,0 @@
use crate::window::MAIN_WINDOW_PREFIX;
use hex_color::HexColor;
use log::warn;
use objc::{msg_send, sel, sel_impl};
use rand::distr::Alphanumeric;
use rand::Rng;
use tauri::{
plugin::{Builder, TauriPlugin},
Emitter, Listener, Manager, Runtime, Window, WindowEvent,
};
const WINDOW_CONTROL_PAD_X: f64 = 13.0;
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
struct UnsafeWindowHandle(*mut std::ffi::c_void);
unsafe impl Send for UnsafeWindowHandle {}
unsafe impl Sync for UnsafeWindowHandle {}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("mac_window")
.on_window_ready(|window| {
#[cfg(target_os = "macos")]
{
setup_traffic_light_positioner(&window);
let h = window.app_handle();
let window_for_theme = window.clone();
let id1 = h.listen("yaak_bg_changed", move |ev| {
let color_str: String = match serde_json::from_str(ev.payload()) {
Ok(color) => color,
Err(err) => {
warn!("Failed to JSON parse color '{}': {}", ev.payload(), err);
return;
}
};
match HexColor::parse_rgb(color_str.trim()) {
Ok(color) => {
update_window_theme(window_for_theme.clone(), color);
}
Err(err) => {
warn!("Failed to parse background color '{}': {}", color_str, err)
}
}
});
let window_for_title = window.clone();
let id2 = h.listen("yaak_title_changed", move |ev| {
let title: String = match serde_json::from_str(ev.payload()) {
Ok(title) => title,
Err(err) => {
warn!("Failed to parse window title \"{}\": {}", ev.payload(), err);
return;
}
};
update_window_title(window_for_title.clone(), title);
});
let h = h.clone();
window.on_window_event(move |e| {
match e {
WindowEvent::Destroyed => {
h.unlisten(id1);
h.unlisten(id2);
}
_ => {}
};
});
}
return;
})
.build()
}
#[cfg(target_os = "macos")]
fn update_window_title<R: Runtime>(window: Window<R>, title: String) {
use cocoa::{appkit::NSWindow, base::nil, foundation::NSString};
unsafe {
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
let window2 = window.clone();
let label = window.label().to_string();
let _ = window.run_on_main_thread(move || {
let win_title = NSString::alloc(nil).init_str(&title);
let handle = window_handle;
NSWindow::setTitle_(handle.0 as cocoa::base::id, win_title);
position_traffic_lights(
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
label,
);
});
}
}
#[cfg(target_os = "macos")]
fn update_window_theme<R: Runtime>(window: Window<R>, color: HexColor) {
use cocoa::appkit::{
NSAppearance, NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSWindow,
};
let brightness = (color.r as u64 + color.g as u64 + color.b as u64) / 3;
let label = window.label().to_string();
unsafe {
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
let window2 = window.clone();
let _ = window.run_on_main_thread(move || {
let handle = window_handle;
let selected_appearance = if brightness >= 128 {
NSAppearance(NSAppearanceNameVibrantLight)
} else {
NSAppearance(NSAppearanceNameVibrantDark)
};
NSWindow::setAppearance(handle.0 as cocoa::base::id, selected_appearance);
position_traffic_lights(
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
label,
);
});
}
}
#[cfg(target_os = "macos")]
fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64, label: String) {
if !label.starts_with(MAIN_WINDOW_PREFIX) {
return;
}
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
use cocoa::foundation::NSRect;
let ns_window = ns_window_handle.0 as cocoa::base::id;
unsafe {
let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
let miniaturize =
ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
let zoom = ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
let title_bar_container_view = close.superview().superview();
let close_rect: NSRect = msg_send![close, frame];
let button_height = close_rect.size.height;
let title_bar_frame_height = button_height + y;
let mut title_bar_rect = NSView::frame(title_bar_container_view);
title_bar_rect.size.height = title_bar_frame_height;
title_bar_rect.origin.y = NSView::frame(ns_window).size.height - title_bar_frame_height;
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
let window_buttons = vec![close, miniaturize, zoom];
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect: NSRect = NSView::frame(button);
rect.origin.x = x + (i as f64 * space_between);
button.setFrameOrigin(rect.origin);
}
}
}
#[cfg(target_os = "macos")]
#[derive(Debug)]
struct WindowState<R: Runtime> {
window: Window<R>,
}
#[cfg(target_os = "macos")]
pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
use cocoa::appkit::NSWindow;
use cocoa::base::{id, BOOL};
use cocoa::delegate;
use cocoa::foundation::NSUInteger;
use objc::runtime::{Object, Sel};
use std::ffi::c_void;
position_traffic_lights(
UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
window.label().to_string(),
);
// Ensure they stay in place while resizing the window.
fn with_window_state<R: Runtime, F: FnOnce(&mut WindowState<R>) -> T, T>(
this: &Object,
func: F,
) {
let ptr = unsafe {
let x: *mut c_void = *this.get_ivar("app_box");
&mut *(x as *mut WindowState<R>)
};
func(ptr);
}
unsafe {
let ns_win =
window.ns_window().expect("NS Window should exist to mount traffic light delegate.")
as id;
let current_delegate: id = ns_win.delegate();
extern "C" fn on_window_should_close(this: &Object, _cmd: Sel, sender: id) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, windowShouldClose: sender]
}
}
extern "C" fn on_window_will_close(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowWillClose: notification];
}
}
extern "C" fn on_window_did_resize<R: Runtime>(this: &Object, _cmd: Sel, notification: id) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
let id = state
.window
.ns_window()
.expect("NS window should exist on state to handle resize")
as id;
position_traffic_lights(
UnsafeWindowHandle(id as *mut c_void),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
state.window.label().to_string(),
);
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidResize: notification];
}
}
extern "C" fn on_window_did_move(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidMove: notification];
}
}
extern "C" fn on_window_did_change_backing_properties(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification];
}
}
extern "C" fn on_window_did_become_key(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidBecomeKey: notification];
}
}
extern "C" fn on_window_did_resign_key(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidResignKey: notification];
}
}
extern "C" fn on_dragging_entered(this: &Object, _cmd: Sel, notification: id) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, draggingEntered: notification]
}
}
extern "C" fn on_prepare_for_drag_operation(
this: &Object,
_cmd: Sel,
notification: id,
) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, prepareForDragOperation: notification]
}
}
extern "C" fn on_perform_drag_operation(this: &Object, _cmd: Sel, sender: id) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, performDragOperation: sender]
}
}
extern "C" fn on_conclude_drag_operation(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, concludeDragOperation: notification];
}
}
extern "C" fn on_dragging_exited(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, draggingExited: notification];
}
}
extern "C" fn on_window_will_use_full_screen_presentation_options(
this: &Object,
_cmd: Sel,
window: id,
proposed_options: NSUInteger,
) -> NSUInteger {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
}
}
extern "C" fn on_window_did_enter_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state.window.emit("did-enter-fullscreen", ()).expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
}
}
extern "C" fn on_window_will_enter_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state.window.emit("will-enter-fullscreen", ()).expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
}
}
extern "C" fn on_window_did_exit_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state.window.emit("did-exit-fullscreen", ()).expect("Failed to emit event");
let id = state.window.ns_window().expect("Failed to emit event") as id;
position_traffic_lights(
UnsafeWindowHandle(id as *mut c_void),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
state.window.label().to_string(),
);
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidExitFullScreen: notification];
}
}
extern "C" fn on_window_will_exit_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state.window.emit("will-exit-fullscreen", ()).expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowWillExitFullScreen: notification];
}
}
extern "C" fn on_window_did_fail_to_enter_full_screen(
this: &Object,
_cmd: Sel,
window: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window];
}
}
extern "C" fn on_effective_appearance_did_change(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification];
}
}
extern "C" fn on_effective_appearance_did_changed_on_main_thread(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![
super_del,
effectiveAppearanceDidChangedOnMainThread: notification
];
}
}
// Are we de-allocing this properly? (I miss safe Rust :( )
let window_label = window.label().to_string();
let app_state = WindowState {
window: window.clone(),
};
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
let random_str: String =
rand::rng().sample_iter(&Alphanumeric).take(20).map(char::from).collect();
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
// delegate with the same name.
let delegate_name = format!("windowDelegate_{}_{}", window_label, random_str);
ns_win.setDelegate_(delegate!(&delegate_name, {
window: id = ns_win,
app_box: *mut c_void = app_box,
toolbar: id = cocoa::base::nil,
super_delegate: id = current_delegate,
(windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
(windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
(windowDidResize:) => on_window_did_resize::<R> as extern fn(&Object, Sel, id),
(windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
(windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
(windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
(windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id),
(draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL,
(prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
(performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
(concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
(draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
(window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen::<R> as extern fn(&Object, Sel, id),
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen::<R> as extern fn(&Object, Sel, id),
(windowDidExitFullScreen:) => on_window_did_exit_full_screen::<R> as extern fn(&Object, Sel, id),
(windowWillExitFullScreen:) => on_window_will_exit_full_screen::<R> as extern fn(&Object, Sel, id),
(windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
(effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
(effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
}))
}
}