/* * This file is part of espanso. * * Copyright (C) 2020 Federico Terzi * * espanso is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * espanso is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with espanso. If not, see . */ use crate::{config::Configs, extension::ExtensionResult, ui::modulo::ModuloManager}; use log::{error, warn}; use serde_yaml::{Mapping, Value}; use std::collections::HashMap; pub struct FormExtension { manager: ModuloManager, } impl FormExtension { pub fn new(config: &Configs) -> FormExtension { let manager = ModuloManager::new(config); FormExtension { manager } } } impl super::Extension for FormExtension { fn name(&self) -> String { "form".to_owned() } fn calculate( &self, params: &Mapping, _: &Vec, _: &HashMap, ) -> super::ExtensionOut { let layout = params.get(&Value::from("layout")); let layout = if let Some(value) = layout { value.as_str().unwrap_or_default().to_string() } else { error!("invoking form extension without specifying a layout"); return Err(super::ExtensionError::Internal); }; let mut form_config = Mapping::new(); form_config.insert(Value::from("title"), Value::from("espanso")); form_config.insert(Value::from("layout"), Value::from(layout)); if let Some(fields) = params.get(&Value::from("fields")) { form_config.insert(Value::from("fields"), fields.clone()); } if let Some(icon_path) = crate::context::get_icon_path() { form_config.insert( Value::from("icon"), Value::from(icon_path.to_string_lossy().to_string()), ); } let serialized_config: String = serde_yaml::to_string(&form_config).expect("unable to serialize form config"); let output = self .manager .invoke(&["form", "-i", "-"], &serialized_config); // On macOS and Windows, after the form closes we have to wait until the user releases the modifier keys on_form_close(); if let Some(output) = output { let json: Result, _> = serde_json::from_str(&output); match json { Ok(json) => { // Check if the JSON is empty. In those cases, it means the user exited // the form before submitting it, therefore the expansion should stop if json.is_empty() { return Err(super::ExtensionError::Aborted); } return Ok(Some(ExtensionResult::Multiple(json))); } Err(error) => { error!("modulo json parsing error: {}", error); return Err(super::ExtensionError::Internal); } } } else { error!("modulo form didn't return any output"); return Err(super::ExtensionError::Internal); } } } #[cfg(target_os = "linux")] fn on_form_close() { // NOOP on Linux } #[cfg(target_os = "windows")] fn on_form_close() { let released = crate::keyboard::windows::wait_for_modifiers_release(); if !released { warn!("Wait for modifiers release timed out! Please after closing the form, release your modifiers keys (CTRL, CMD, ALT, SHIFT)"); } } #[cfg(target_os = "macos")] fn on_form_close() { let released = crate::keyboard::macos::wait_for_modifiers_release(); if !released { warn!("Wait for modifiers release timed out! Please after closing the form, release your modifiers keys (CTRL, CMD, ALT, SHIFT)"); } }