diff --git a/Cargo.lock b/Cargo.lock index 244170a..0ba00ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,7 +572,7 @@ dependencies = [ [[package]] name = "espanso" -version = "2.1.0-alpha" +version = "2.1.1-alpha" dependencies = [ "anyhow", "caps", diff --git a/espanso-clipboard/src/cocoa/mod.rs b/espanso-clipboard/src/cocoa/mod.rs index d842592..6802851 100644 --- a/espanso-clipboard/src/cocoa/mod.rs +++ b/espanso-clipboard/src/cocoa/mod.rs @@ -24,7 +24,7 @@ use std::{ path::PathBuf, }; -use crate::Clipboard; +use crate::{Clipboard, ClipboardOperationOptions}; use anyhow::Result; use log::error; use thiserror::Error; @@ -38,7 +38,7 @@ impl CocoaClipboard { } impl Clipboard for CocoaClipboard { - fn get_text(&self) -> Option { + fn get_text(&self, _: &ClipboardOperationOptions) -> Option { let mut buffer: [i8; 2048] = [0; 2048]; let native_result = unsafe { ffi::clipboard_get_text(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) }; @@ -50,7 +50,7 @@ impl Clipboard for CocoaClipboard { } } - fn set_text(&self, text: &str) -> anyhow::Result<()> { + fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> { let string = CString::new(text)?; let native_result = unsafe { ffi::clipboard_set_text(string.as_ptr()) }; if native_result > 0 { @@ -60,7 +60,11 @@ impl Clipboard for CocoaClipboard { } } - fn set_image(&self, image_path: &std::path::Path) -> anyhow::Result<()> { + fn set_image( + &self, + image_path: &std::path::Path, + _: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { if !image_path.exists() || !image_path.is_file() { return Err(CocoaClipboardError::ImageNotFound(image_path.to_path_buf()).into()); } @@ -75,7 +79,12 @@ impl Clipboard for CocoaClipboard { } } - fn set_html(&self, html: &str, fallback_text: Option<&str>) -> anyhow::Result<()> { + fn set_html( + &self, + html: &str, + fallback_text: Option<&str>, + _: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { let html_string = CString::new(html)?; let fallback_string = CString::new(fallback_text.unwrap_or_default())?; let fallback_ptr = if fallback_text.is_some() { diff --git a/espanso-clipboard/src/lib.rs b/espanso-clipboard/src/lib.rs index 077728b..7416ce4 100644 --- a/espanso-clipboard/src/lib.rs +++ b/espanso-clipboard/src/lib.rs @@ -37,10 +37,28 @@ mod wayland; mod cocoa; pub trait Clipboard { - fn get_text(&self) -> Option; - fn set_text(&self, text: &str) -> Result<()>; - fn set_image(&self, image_path: &Path) -> Result<()>; - fn set_html(&self, html: &str, fallback_text: Option<&str>) -> Result<()>; + fn get_text(&self, options: &ClipboardOperationOptions) -> Option; + fn set_text(&self, text: &str, options: &ClipboardOperationOptions) -> Result<()>; + fn set_image(&self, image_path: &Path, options: &ClipboardOperationOptions) -> Result<()>; + fn set_html( + &self, + html: &str, + fallback_text: Option<&str>, + options: &ClipboardOperationOptions, + ) -> Result<()>; +} + +#[allow(dead_code)] +pub struct ClipboardOperationOptions { + pub use_xclip_backend: bool, +} + +impl Default for ClipboardOperationOptions { + fn default() -> Self { + Self { + use_xclip_backend: false, + } + } } #[allow(dead_code)] @@ -74,8 +92,8 @@ pub fn get_clipboard(_: ClipboardOptions) -> Result> { #[cfg(target_os = "linux")] #[cfg(not(feature = "wayland"))] pub fn get_clipboard(_: ClipboardOptions) -> Result> { - info!("using X11NativeClipboard"); - Ok(Box::new(x11::native::X11NativeClipboard::new()?)) + info!("using X11Clipboard"); + Ok(Box::new(x11::X11Clipboard::new()?)) } #[cfg(target_os = "linux")] diff --git a/espanso-clipboard/src/wayland/fallback/mod.rs b/espanso-clipboard/src/wayland/fallback/mod.rs index c916dac..f566a41 100644 --- a/espanso-clipboard/src/wayland/fallback/mod.rs +++ b/espanso-clipboard/src/wayland/fallback/mod.rs @@ -24,7 +24,7 @@ use std::{ process::Stdio, }; -use crate::{Clipboard, ClipboardOptions}; +use crate::{Clipboard, ClipboardOperationOptions, ClipboardOptions}; use anyhow::Result; use log::{error, warn}; use std::process::Command; @@ -74,7 +74,7 @@ impl WaylandFallbackClipboard { } impl Clipboard for WaylandFallbackClipboard { - fn get_text(&self) -> Option { + fn get_text(&self, _: &ClipboardOperationOptions) -> Option { let timeout = std::time::Duration::from_millis(self.command_timeout); match Command::new("wl-paste") .arg("--no-newline") @@ -116,11 +116,15 @@ impl Clipboard for WaylandFallbackClipboard { } } - fn set_text(&self, text: &str) -> anyhow::Result<()> { + fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> { self.invoke_command_with_timeout(&mut Command::new("wl-copy"), text.as_bytes(), "wl-copy") } - fn set_image(&self, image_path: &std::path::Path) -> anyhow::Result<()> { + fn set_image( + &self, + image_path: &std::path::Path, + _: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { if !image_path.exists() || !image_path.is_file() { return Err(WaylandFallbackClipboardError::ImageNotFound(image_path.to_path_buf()).into()); } @@ -137,7 +141,12 @@ impl Clipboard for WaylandFallbackClipboard { ) } - fn set_html(&self, html: &str, _fallback_text: Option<&str>) -> anyhow::Result<()> { + fn set_html( + &self, + html: &str, + _fallback_text: Option<&str>, + _: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { self.invoke_command_with_timeout( &mut Command::new("wl-copy").arg("--type").arg("text/html"), html.as_bytes(), diff --git a/espanso-clipboard/src/win32/mod.rs b/espanso-clipboard/src/win32/mod.rs index c4a3143..5d8023d 100644 --- a/espanso-clipboard/src/win32/mod.rs +++ b/espanso-clipboard/src/win32/mod.rs @@ -21,7 +21,7 @@ mod ffi; use std::{ffi::CString, path::PathBuf}; -use crate::Clipboard; +use crate::{Clipboard, ClipboardOperationOptions}; use anyhow::Result; use log::error; use thiserror::Error; @@ -36,7 +36,7 @@ impl Win32Clipboard { } impl Clipboard for Win32Clipboard { - fn get_text(&self) -> Option { + fn get_text(&self, _: &ClipboardOperationOptions) -> Option { let mut buffer: [u16; 2048] = [0; 2048]; let native_result = unsafe { ffi::clipboard_get_text(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) }; @@ -48,7 +48,7 @@ impl Clipboard for Win32Clipboard { } } - fn set_text(&self, text: &str) -> anyhow::Result<()> { + fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> { let string = U16CString::from_str(text)?; let native_result = unsafe { ffi::clipboard_set_text(string.as_ptr()) }; if native_result > 0 { @@ -58,7 +58,11 @@ impl Clipboard for Win32Clipboard { } } - fn set_image(&self, image_path: &std::path::Path) -> anyhow::Result<()> { + fn set_image( + &self, + image_path: &std::path::Path, + _: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { if !image_path.exists() || !image_path.is_file() { return Err(Win32ClipboardError::ImageNotFound(image_path.to_path_buf()).into()); } @@ -73,7 +77,12 @@ impl Clipboard for Win32Clipboard { } } - fn set_html(&self, html: &str, fallback_text: Option<&str>) -> anyhow::Result<()> { + fn set_html( + &self, + html: &str, + fallback_text: Option<&str>, + _: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { let html_descriptor = generate_html_descriptor(html); let html_string = CString::new(html_descriptor)?; let fallback_string = U16CString::from_str(fallback_text.unwrap_or_default())?; diff --git a/espanso-clipboard/src/x11/mod.rs b/espanso-clipboard/src/x11/mod.rs index cccb648..9e658e3 100644 --- a/espanso-clipboard/src/x11/mod.rs +++ b/espanso-clipboard/src/x11/mod.rs @@ -17,4 +17,66 @@ * along with espanso. If not, see . */ -pub(crate) mod native; +use anyhow::Result; + +use crate::{Clipboard, ClipboardOperationOptions}; + +mod native; +mod xclip; + +pub(crate) struct X11Clipboard { + native_backend: native::X11NativeClipboard, + xclip_backend: xclip::XClipClipboard, +} + +impl X11Clipboard { + pub fn new() -> Result { + Ok(Self { + native_backend: native::X11NativeClipboard::new()?, + xclip_backend: xclip::XClipClipboard::new(), + }) + } +} + +impl Clipboard for X11Clipboard { + fn get_text(&self, options: &ClipboardOperationOptions) -> Option { + if options.use_xclip_backend { + self.xclip_backend.get_text(options) + } else { + self.native_backend.get_text(options) + } + } + + fn set_text(&self, text: &str, options: &ClipboardOperationOptions) -> anyhow::Result<()> { + if options.use_xclip_backend { + self.xclip_backend.set_text(text, options) + } else { + self.native_backend.set_text(text, options) + } + } + + fn set_image( + &self, + image_path: &std::path::Path, + options: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { + if options.use_xclip_backend { + self.xclip_backend.set_image(image_path, options) + } else { + self.native_backend.set_image(image_path, options) + } + } + + fn set_html( + &self, + html: &str, + fallback_text: Option<&str>, + options: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { + if options.use_xclip_backend { + self.xclip_backend.set_html(html, fallback_text, options) + } else { + self.native_backend.set_html(html, fallback_text, options) + } + } +} diff --git a/espanso-clipboard/src/x11/native/mod.rs b/espanso-clipboard/src/x11/native/mod.rs index de5a1f1..6cba718 100644 --- a/espanso-clipboard/src/x11/native/mod.rs +++ b/espanso-clipboard/src/x11/native/mod.rs @@ -23,7 +23,7 @@ use std::{ path::PathBuf, }; -use crate::Clipboard; +use crate::{Clipboard, ClipboardOperationOptions}; use anyhow::Result; use std::os::raw::c_char; use thiserror::Error; @@ -39,7 +39,7 @@ impl X11NativeClipboard { } impl Clipboard for X11NativeClipboard { - fn get_text(&self) -> Option { + fn get_text(&self, _: &ClipboardOperationOptions) -> Option { let mut buffer: [c_char; 2048] = [0; 2048]; let native_result = unsafe { ffi::clipboard_x11_get_text(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) }; @@ -51,7 +51,7 @@ impl Clipboard for X11NativeClipboard { } } - fn set_text(&self, text: &str) -> anyhow::Result<()> { + fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> { let string = CString::new(text)?; let native_result = unsafe { ffi::clipboard_x11_set_text(string.as_ptr()) }; if native_result > 0 { @@ -61,7 +61,11 @@ impl Clipboard for X11NativeClipboard { } } - fn set_image(&self, image_path: &std::path::Path) -> anyhow::Result<()> { + fn set_image( + &self, + image_path: &std::path::Path, + _: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { if !image_path.exists() || !image_path.is_file() { return Err(X11NativeClipboardError::ImageNotFound(image_path.to_path_buf()).into()); } @@ -80,7 +84,12 @@ impl Clipboard for X11NativeClipboard { } } - fn set_html(&self, html: &str, fallback_text: Option<&str>) -> anyhow::Result<()> { + fn set_html( + &self, + html: &str, + fallback_text: Option<&str>, + _: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { let html_string = CString::new(html)?; let fallback_string = CString::new(fallback_text.unwrap_or_default())?; let fallback_ptr = if fallback_text.is_some() { diff --git a/espanso-clipboard/src/x11/xclip/mod.rs b/espanso-clipboard/src/x11/xclip/mod.rs new file mode 100644 index 0000000..1136dff --- /dev/null +++ b/espanso-clipboard/src/x11/xclip/mod.rs @@ -0,0 +1,139 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 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 anyhow::bail; +use log::error; +use std::io::Write; +use std::process::{Command, Stdio}; + +use crate::{Clipboard, ClipboardOperationOptions}; + +pub struct XClipClipboard { + is_xclip_available: bool, +} + +impl XClipClipboard { + pub fn new() -> Self { + let command = Command::new("xclipz").arg("-h").output(); + let is_xclip_available = command + .map(|output| output.status.success()) + .unwrap_or(false); + + Self { is_xclip_available } + } +} + +impl Clipboard for XClipClipboard { + fn get_text(&self, _: &ClipboardOperationOptions) -> Option { + if !self.is_xclip_available { + error!("attempted to use XClipClipboard, but `xclip` command can't be called"); + return None; + } + + match Command::new("xclip").args(&["-o", "-sel", "clip"]).output() { + Ok(output) => { + if output.status.success() { + let s = String::from_utf8_lossy(&output.stdout); + return Some(s.to_string()); + } + } + Err(error) => { + error!("xclip reported an error: {}", error); + } + } + + None + } + + fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> { + if !self.is_xclip_available { + bail!("attempted to use XClipClipboard, but `xclip` command can't be called"); + } + + let mut child = Command::new("xclip") + .args(&["-sel", "clip"]) + .stdin(Stdio::piped()) + .spawn()?; + + let stdin = child.stdin.as_mut(); + if let Some(input) = stdin { + input.write_all(text.as_bytes())?; + child.wait()?; + } + + Ok(()) + } + + fn set_image( + &self, + image_path: &std::path::Path, + _: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { + if !self.is_xclip_available { + bail!("attempted to use XClipClipboard, but `xclip` command can't be called"); + } + + let extension = image_path.extension(); + let mime = match extension { + Some(ext) => { + let ext = ext.to_string_lossy().to_lowercase(); + match ext.as_ref() { + "png" => "image/png", + "jpg" | "jpeg" => "image/jpeg", + "gif" => "image/gif", + "svg" => "image/svg", + _ => "image/png", + } + } + None => "image/png", + }; + + let image_path = image_path.to_string_lossy(); + + Command::new("xclip") + .args(&["-selection", "clipboard", "-t", mime, "-i", &image_path]) + .spawn()?; + + Ok(()) + } + + fn set_html( + &self, + html: &str, + _: Option<&str>, + _: &ClipboardOperationOptions, + ) -> anyhow::Result<()> { + if !self.is_xclip_available { + bail!("attempted to use XClipClipboard, but `xclip` command can't be called"); + } + + let mut child = Command::new("xclip") + .args(&["-sel", "clip", "-t", "text/html"]) + .stdin(Stdio::piped()) + .spawn()?; + + let stdin = child.stdin.as_mut(); + if let Some(input) = stdin { + input.write_all(html.as_bytes())?; + child.wait()?; + } + + Ok(()) + } +} diff --git a/espanso-config/src/config/mod.rs b/espanso-config/src/config/mod.rs index dd88929..0992372 100644 --- a/espanso-config/src/config/mod.rs +++ b/espanso-config/src/config/mod.rs @@ -150,6 +150,10 @@ pub trait Config: Send + Sync { // If false, avoid showing the SecureInput notification on macOS fn secure_input_notification(&self) -> bool; + // If true, use the `xclip` command to implement the clipboard instead of + // the built-in native module on X11. + fn x11_use_xclip_backend(&self) -> bool; + // If true, filter out keyboard events without an explicit HID device source on Windows. // This is needed to filter out the software-generated events, including // those from espanso, but might need to be disabled when using some software-level keyboards. @@ -193,6 +197,7 @@ pub trait Config: Send + Sync { show_notifications: {:?} secure_input_notification: {:?} + x11_use_xclip_backend: {:?} win32_exclude_orphan_events: {:?} win32_keyboard_layout_cache_interval: {:?} @@ -224,6 +229,7 @@ pub trait Config: Send + Sync { self.show_notifications(), self.secure_input_notification(), + self.x11_use_xclip_backend(), self.win32_exclude_orphan_events(), self.win32_keyboard_layout_cache_interval(), diff --git a/espanso-config/src/config/parse/mod.rs b/espanso-config/src/config/parse/mod.rs index bd39ad4..94f174d 100644 --- a/espanso-config/src/config/parse/mod.rs +++ b/espanso-config/src/config/parse/mod.rs @@ -46,6 +46,7 @@ pub(crate) struct ParsedConfig { pub secure_input_notification: Option, pub win32_exclude_orphan_events: Option, pub win32_keyboard_layout_cache_interval: Option, + pub x11_use_xclip_backend: Option, pub pre_paste_delay: Option, pub restore_clipboard_delay: Option, diff --git a/espanso-config/src/config/parse/yaml.rs b/espanso-config/src/config/parse/yaml.rs index 824429c..d666662 100644 --- a/espanso-config/src/config/parse/yaml.rs +++ b/espanso-config/src/config/parse/yaml.rs @@ -112,6 +112,9 @@ pub(crate) struct YAMLConfig { #[serde(default)] pub win32_keyboard_layout_cache_interval: Option, + #[serde(default)] + pub x11_use_xclip_backend: Option, + // Include/Exclude #[serde(default)] pub includes: Option>, @@ -201,6 +204,7 @@ impl TryFrom for ParsedConfig { win32_exclude_orphan_events: yaml_config.win32_exclude_orphan_events, win32_keyboard_layout_cache_interval: yaml_config.win32_keyboard_layout_cache_interval, + x11_use_xclip_backend: yaml_config.x11_use_xclip_backend, use_standard_includes: yaml_config.use_standard_includes, includes: yaml_config.includes, @@ -258,6 +262,7 @@ mod tests { secure_input_notification: false win32_exclude_orphan_events: false win32_keyboard_layout_cache_interval: 300 + x11_use_xclip_backend: true use_standard_includes: true includes: ["test1"] @@ -311,6 +316,7 @@ mod tests { secure_input_notification: Some(false), win32_exclude_orphan_events: Some(false), win32_keyboard_layout_cache_interval: Some(300), + x11_use_xclip_backend: Some(true), pre_paste_delay: Some(300), evdev_modifier_delay: Some(40), diff --git a/espanso-config/src/config/resolve.rs b/espanso-config/src/config/resolve.rs index afcad9b..8738085 100644 --- a/espanso-config/src/config/resolve.rs +++ b/espanso-config/src/config/resolve.rs @@ -330,6 +330,10 @@ impl Config for ResolvedConfig { .win32_keyboard_layout_cache_interval .unwrap_or(2000) } + + fn x11_use_xclip_backend(&self) -> bool { + self.parsed.x11_use_xclip_backend.unwrap_or(false) + } } impl ResolvedConfig { @@ -413,6 +417,7 @@ impl ResolvedConfig { secure_input_notification, win32_exclude_orphan_events, win32_keyboard_layout_cache_interval, + x11_use_xclip_backend, includes, excludes, extra_includes, diff --git a/espanso-config/src/legacy/mod.rs b/espanso-config/src/legacy/mod.rs index 8347c8e..b67e0f7 100644 --- a/espanso-config/src/legacy/mod.rs +++ b/espanso-config/src/legacy/mod.rs @@ -398,6 +398,10 @@ impl Config for LegacyInteropConfig { fn win32_keyboard_layout_cache_interval(&self) -> i64 { 2000 } + + fn x11_use_xclip_backend(&self) -> bool { + false + } } struct LegacyMatchGroup { diff --git a/espanso-engine/src/dispatch/default.rs b/espanso-engine/src/dispatch/default.rs index bb14a77..146b2c2 100644 --- a/espanso-engine/src/dispatch/default.rs +++ b/espanso-engine/src/dispatch/default.rs @@ -17,7 +17,9 @@ * along with espanso. If not, see . */ -use super::{ContextMenuHandler, Event, IconHandler, ImageInjector, SecureInputManager}; +use super::{ + ContextMenuHandler, Event, IconHandler, ImageInjector, SecureInputManager, TextUIHandler, +}; use super::{Dispatcher, Executor, HtmlInjector, KeyInjector, ModeProvider, TextInjector}; pub struct DefaultDispatcher<'a> { @@ -36,6 +38,7 @@ impl<'a> DefaultDispatcher<'a> { context_menu_handler: &'a dyn ContextMenuHandler, icon_handler: &'a dyn IconHandler, secure_input_manager: &'a dyn SecureInputManager, + text_ui_handler: &'a dyn TextUIHandler, ) -> Self { Self { executors: vec![ @@ -62,6 +65,9 @@ impl<'a> DefaultDispatcher<'a> { Box::new(super::executor::secure_input::SecureInputExecutor::new( secure_input_manager, )), + Box::new(super::executor::text_ui::TextUIExecutor::new( + text_ui_handler, + )), ], } } diff --git a/espanso-engine/src/dispatch/executor/mod.rs b/espanso-engine/src/dispatch/executor/mod.rs index 31bb6d0..e9ac066 100644 --- a/espanso-engine/src/dispatch/executor/mod.rs +++ b/espanso-engine/src/dispatch/executor/mod.rs @@ -24,3 +24,4 @@ pub mod image_inject; pub mod key_inject; pub mod secure_input; pub mod text_inject; +pub mod text_ui; diff --git a/espanso-engine/src/dispatch/executor/text_ui.rs b/espanso-engine/src/dispatch/executor/text_ui.rs new file mode 100644 index 0000000..e08ba86 --- /dev/null +++ b/espanso-engine/src/dispatch/executor/text_ui.rs @@ -0,0 +1,63 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 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::event::EventType; +use crate::{dispatch::Executor, event::Event}; +use anyhow::Result; +use log::error; + +pub trait TextUIHandler { + fn show_text(&self, title: &str, text: &str) -> Result<()>; + fn show_logs(&self) -> Result<()>; +} + +pub struct TextUIExecutor<'a> { + handler: &'a dyn TextUIHandler, +} + +impl<'a> TextUIExecutor<'a> { + pub fn new(handler: &'a dyn TextUIHandler) -> Self { + Self { handler } + } +} + +impl<'a> Executor for TextUIExecutor<'a> { + fn execute(&self, event: &Event) -> bool { + if let EventType::ShowText(show_text_event) = &event.etype { + if let Err(error) = self + .handler + .show_text(&show_text_event.title, &show_text_event.text) + { + error!("text UI handler reported an error: {:?}", error); + } + + return true; + } else if let EventType::ShowLogs = &event.etype { + if let Err(error) = self.handler.show_logs() { + error!("text UI handler reported an error: {:?}", error); + } + + return true; + } + + false + } +} + +// TODO: test diff --git a/espanso-engine/src/dispatch/mod.rs b/espanso-engine/src/dispatch/mod.rs index d2959ff..753fffc 100644 --- a/espanso-engine/src/dispatch/mod.rs +++ b/espanso-engine/src/dispatch/mod.rs @@ -38,6 +38,7 @@ pub use executor::image_inject::ImageInjector; pub use executor::key_inject::KeyInjector; pub use executor::secure_input::SecureInputManager; pub use executor::text_inject::{Mode, ModeProvider, TextInjector}; +pub use executor::text_ui::{TextUIExecutor, TextUIHandler}; #[allow(clippy::too_many_arguments)] pub fn default<'a>( @@ -50,6 +51,7 @@ pub fn default<'a>( context_menu_handler: &'a dyn ContextMenuHandler, icon_handler: &'a dyn IconHandler, secure_input_manager: &'a dyn SecureInputManager, + text_ui_handler: &'a dyn TextUIHandler, ) -> impl Dispatcher + 'a { default::DefaultDispatcher::new( event_injector, @@ -61,5 +63,6 @@ pub fn default<'a>( context_menu_handler, icon_handler, secure_input_manager, + text_ui_handler, ) } diff --git a/espanso-engine/src/event/mod.rs b/espanso-engine/src/event/mod.rs index 2042588..2100cba 100644 --- a/espanso-engine/src/event/mod.rs +++ b/espanso-engine/src/event/mod.rs @@ -101,6 +101,8 @@ pub enum EventType { IconStatusChange(ui::IconStatusChangeEvent), DisplaySecureInputTroubleshoot, ShowSearchBar, + ShowText(ui::ShowTextEvent), + ShowLogs, // Other LaunchSecureInputAutoFix, diff --git a/espanso-engine/src/event/ui.rs b/espanso-engine/src/event/ui.rs index b172c45..f23bbb9 100644 --- a/espanso-engine/src/event/ui.rs +++ b/espanso-engine/src/event/ui.rs @@ -52,3 +52,9 @@ pub enum IconStatus { Disabled, SecureInputDisabled, } + +#[derive(Debug, Clone, PartialEq)] +pub struct ShowTextEvent { + pub title: String, + pub text: String, +} diff --git a/espanso-engine/src/process/middleware/context_menu.rs b/espanso-engine/src/process/middleware/context_menu.rs index 79de2e3..9ee53fd 100644 --- a/espanso-engine/src/process/middleware/context_menu.rs +++ b/espanso-engine/src/process/middleware/context_menu.rs @@ -32,6 +32,7 @@ const CONTEXT_ITEM_DISABLE: u32 = 3; const CONTEXT_ITEM_SECURE_INPUT_EXPLAIN: u32 = 4; const CONTEXT_ITEM_SECURE_INPUT_TRIGGER_WORKAROUND: u32 = 5; const CONTEXT_ITEM_OPEN_SEARCH: u32 = 6; +const CONTEXT_ITEM_SHOW_LOGS: u32 = 7; pub struct ContextMenuMiddleware { is_enabled: RefCell, @@ -82,6 +83,10 @@ impl Middleware for ContextMenuMiddleware { id: CONTEXT_ITEM_RELOAD, label: "Reload config".to_string(), }), + MenuItem::Simple(SimpleMenuItem { + id: CONTEXT_ITEM_SHOW_LOGS, + label: "Show logs".to_string(), + }), MenuItem::Separator, MenuItem::Simple(SimpleMenuItem { id: CONTEXT_ITEM_EXIT, @@ -155,6 +160,10 @@ impl Middleware for ContextMenuMiddleware { dispatch(Event::caused_by(event.source_id, EventType::ShowSearchBar)); Event::caused_by(event.source_id, EventType::NOOP) } + CONTEXT_ITEM_SHOW_LOGS => { + dispatch(Event::caused_by(event.source_id, EventType::ShowLogs)); + Event::caused_by(event.source_id, EventType::NOOP) + } _ => { // TODO: handle dynamic items todo!() diff --git a/espanso-inject/build.rs b/espanso-inject/build.rs index 16bd1a4..702678d 100644 --- a/espanso-inject/build.rs +++ b/espanso-inject/build.rs @@ -64,6 +64,7 @@ fn cc_config() { println!("cargo:rustc-link-lib=dylib=c++"); println!("cargo:rustc-link-lib=static=espansoinject"); println!("cargo:rustc-link-lib=framework=Cocoa"); + println!("cargo:rustc-link-lib=framework=CoreGraphics"); } fn main() { diff --git a/espanso-inject/src/mac/native.mm b/espanso-inject/src/mac/native.mm index 7a8e27f..cecbb56 100644 --- a/espanso-inject/src/mac/native.mm +++ b/espanso-inject/src/mac/native.mm @@ -20,6 +20,7 @@ #include "native.h" #include #import +#import #include // Events dispatched by espanso are "marked" with a custom location diff --git a/espanso-inject/src/x11/ffi.rs b/espanso-inject/src/x11/ffi.rs index d90d333..e456b3a 100644 --- a/espanso-inject/src/x11/ffi.rs +++ b/espanso-inject/src/x11/ffi.rs @@ -47,17 +47,31 @@ pub struct XModifierKeymap { pub modifiermap: *mut KeyCode, } +// XCreateIC values +#[allow(non_upper_case_globals)] +pub const XIMPreeditNothing: c_int = 0x0008; +#[allow(non_upper_case_globals)] +pub const XIMStatusNothing: c_int = 0x0400; + +#[allow(non_upper_case_globals)] +pub const XNClientWindow_0: &[u8] = b"clientWindow\0"; +#[allow(non_upper_case_globals)] +pub const XNInputStyle_0: &[u8] = b"inputStyle\0"; + +pub enum _XIC {} +pub enum _XIM {} +pub enum _XrmHashBucketRec {} + +#[allow(clippy::upper_case_acronyms)] +pub type XIC = *mut _XIC; +#[allow(clippy::upper_case_acronyms)] +pub type XIM = *mut _XIM; +pub type XrmDatabase = *mut _XrmHashBucketRec; + #[link(name = "X11")] extern "C" { pub fn XOpenDisplay(name: *const c_char) -> *mut Display; pub fn XCloseDisplay(display: *mut Display); - pub fn XLookupString( - event: *const XKeyEvent, - buffer_return: *mut c_char, - bytes_buffer: c_int, - keysym_return: *mut KeySym, - status_in_out: *const c_void, - ) -> c_int; pub fn XDefaultRootWindow(display: *mut Display) -> Window; pub fn XGetInputFocus( display: *mut Display, @@ -82,4 +96,31 @@ extern "C" { ) -> c_int; pub fn XSync(display: *mut Display, discard: c_int) -> c_int; pub fn XQueryKeymap(display: *mut Display, keys_return: *mut u8); + pub fn XOpenIM( + display: *mut Display, + db: XrmDatabase, + res_name: *mut c_char, + res_class: *mut c_char, + ) -> XIM; + pub fn XCreateIC( + input_method: XIM, + p2: *const u8, + p3: c_int, + p4: *const u8, + p5: c_int, + p6: *const c_void, + ) -> XIC; + pub fn XDestroyIC(input_context: XIC); + pub fn XmbResetIC(input_context: XIC) -> *mut c_char; + pub fn Xutf8LookupString( + input_context: XIC, + event: *mut XKeyEvent, + buffer: *mut c_char, + buff_size: c_int, + keysym_return: *mut c_ulong, + status_return: *mut c_int, + ) -> c_int; + pub fn XFilterEvent(event: *mut XKeyEvent, window: c_ulong) -> c_int; + pub fn XCloseIM(input_method: XIM) -> c_int; + pub fn XFree(data: *mut c_void) -> c_int; } diff --git a/espanso-inject/src/x11/mod.rs b/espanso-inject/src/x11/mod.rs index 7878fe5..84d4a6c 100644 --- a/espanso-inject/src/x11/mod.rs +++ b/espanso-inject/src/x11/mod.rs @@ -20,7 +20,7 @@ mod ffi; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, ffi::{CStr, CString}, os::raw::c_char, slice, @@ -28,23 +28,29 @@ use std::{ use ffi::{ Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay, XDefaultRootWindow, - XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XLookupString, - XQueryKeymap, XSendEvent, XSync, XTestFakeKeyEvent, + XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XQueryKeymap, + XSendEvent, XSync, XTestFakeKeyEvent, }; -use log::error; +use libc::c_void; +use log::{debug, error}; -use crate::linux::raw_keys::convert_to_sym_array; -use anyhow::Result; +use crate::{linux::raw_keys::convert_to_sym_array, x11::ffi::Xutf8LookupString}; +use anyhow::{bail, Result}; use thiserror::Error; use crate::{keys, InjectionOptions, Injector}; +use self::ffi::{ + XCloseIM, XCreateIC, XDestroyIC, XFilterEvent, XFree, XIMPreeditNothing, XIMStatusNothing, + XNClientWindow_0, XNInputStyle_0, XOpenIM, XmbResetIC, XIC, +}; + // Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB // keycode set (where ESC is 9). const EVDEV_OFFSET: u32 = 8; #[derive(Clone, Copy, Debug)] -struct KeyRecord { +struct KeyPair { // Keycode code: u32, // Modifier state which combined with the code produces the char @@ -52,6 +58,15 @@ struct KeyRecord { state: u32, } +#[derive(Clone, Copy, Debug)] +struct KeyRecord { + main: KeyPair, + + // Under some keyboard layouts (de, es), a deadkey + // press might be needed to generate the right char + preceding_dead_key: Option, +} + type CharMap = HashMap; type SymMap = HashMap; @@ -76,7 +91,7 @@ impl X11Injector { return Err(X11InjectorError::Init().into()); } - let (char_map, sym_map) = Self::generate_maps(display); + let (char_map, sym_map) = Self::generate_maps(display)?; Ok(Self { display, @@ -85,27 +100,169 @@ impl X11Injector { }) } - fn generate_maps(display: *mut Display) -> (CharMap, SymMap) { + fn generate_maps(display: *mut Display) -> Result<(CharMap, SymMap)> { + debug!("generating key maps"); + let mut char_map = HashMap::new(); let mut sym_map = HashMap::new(); - let root_window = unsafe { XDefaultRootWindow(display) }; + let input_method = unsafe { + XOpenIM( + display, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + }; + if input_method.is_null() { + bail!("could not open input method"); + } + let _im_guard = scopeguard::guard((), |_| { + unsafe { XCloseIM(input_method) }; + }); + + let input_context = unsafe { + XCreateIC( + input_method, + XNInputStyle_0.as_ptr(), + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow_0.as_ptr(), + 0, + std::ptr::null_mut(), + ) + }; + if input_context.is_null() { + bail!("could not open input context"); + } + let _ic_guard = scopeguard::guard((), |_| { + unsafe { XDestroyIC(input_context) }; + }); + + let deadkeys = Self::find_deadkeys(display, &input_context)?; + + // Cycle through all state/code combinations to populate the reverse lookup tables + for key_code in 0..256u32 { + for modifier_state in 0..256u32 { + for dead_key in deadkeys.iter() { + let code_with_offset = key_code + EVDEV_OFFSET; + + let preceding_dead_key = if let Some(dead_key) = dead_key { + let mut dead_key_event = XKeyEvent { + display, + keycode: dead_key.code, + state: dead_key.state, + + // These might not even need to be filled + window: 0, + root: 0, + same_screen: 1, + time: 0, + type_: KeyPress, + x_root: 1, + y_root: 1, + x: 1, + y: 1, + subwindow: 0, + serial: 0, + send_event: 0, + }; + + unsafe { XFilterEvent(&mut dead_key_event, 0) }; + + Some(*dead_key) + } else { + None + }; + + let mut key_event = XKeyEvent { + display, + keycode: code_with_offset, + state: modifier_state, + + // These might not even need to be filled + window: 0, + root: 0, + same_screen: 1, + time: 0, + type_: KeyPress, + x_root: 1, + y_root: 1, + x: 1, + y: 1, + subwindow: 0, + serial: 0, + send_event: 0, + }; + + unsafe { XFilterEvent(&mut key_event, 0) }; + let mut sym: KeySym = 0; + let mut buffer: [c_char; 10] = [0; 10]; + + let result = unsafe { + Xutf8LookupString( + input_context, + &mut key_event, + buffer.as_mut_ptr(), + (buffer.len() - 1) as i32, + &mut sym, + std::ptr::null_mut(), + ) + }; + + let key_record = KeyRecord { + main: KeyPair { + code: code_with_offset, + state: modifier_state, + }, + preceding_dead_key, + }; + + // Keysym was found + if sym != 0 { + sym_map.entry(sym).or_insert(key_record); + }; + + // Char was found + if result > 0 { + let raw_string = unsafe { CStr::from_ptr(buffer.as_ptr()) }; + let string = raw_string.to_string_lossy().to_string(); + char_map.entry(string).or_insert(key_record); + }; + + // We need to reset the context state to prevent + // deadkeys effect to propagate to the next combination + let _reset = unsafe { XmbResetIC(input_context) }; + unsafe { XFree(_reset as *mut c_void) }; + } + } + } + + debug!("Populated char_map with {} symbols", char_map.len()); + debug!("Populated sym_map with {} symbols", sym_map.len()); + debug!("Detected {} dead key combinations", deadkeys.len()); + + Ok((char_map, sym_map)) + } + + fn find_deadkeys(display: *mut Display, input_context: &XIC) -> Result>> { + let mut deadkeys = vec![None]; + let mut seen_keysyms: HashSet = HashSet::new(); // Cycle through all state/code combinations to populate the reverse lookup tables for key_code in 0..256u32 { for modifier_state in 0..256u32 { let code_with_offset = key_code + EVDEV_OFFSET; - let event = XKeyEvent { + let mut event = XKeyEvent { display, keycode: code_with_offset, state: modifier_state, // These might not even need to be filled - window: root_window, - root: root_window, + window: 0, + root: 0, same_screen: 1, time: 0, - type_: KeyRelease, + type_: KeyPress, x_root: 1, y_root: 1, x: 1, @@ -115,38 +272,38 @@ impl X11Injector { send_event: 0, }; - let mut sym: KeySym = 0; - let mut buffer: [c_char; 10] = [0; 10]; - let result = unsafe { - XLookupString( - &event, - buffer.as_mut_ptr(), - (buffer.len() - 1) as i32, - &mut sym, - std::ptr::null(), - ) - }; + let filter = unsafe { XFilterEvent(&mut event, 0) }; + if filter == 1 { + let mut sym: KeySym = 0; + let mut buffer: [c_char; 10] = [0; 10]; - let key_record = KeyRecord { - code: code_with_offset, - state: modifier_state, - }; + unsafe { + Xutf8LookupString( + *input_context, + &mut event, + buffer.as_mut_ptr(), + (buffer.len() - 1) as i32, + &mut sym, + std::ptr::null_mut(), + ) + }; - // Keysym was found - if sym != 0 { - sym_map.entry(sym).or_insert(key_record); + if sym != 0 && !seen_keysyms.contains(&sym) { + let key_record = KeyPair { + code: code_with_offset, + state: modifier_state, + }; + deadkeys.push(Some(key_record)); + seen_keysyms.insert(sym); + } } - // Char was found - if result > 0 { - let raw_string = unsafe { CStr::from_ptr(buffer.as_ptr()) }; - let string = raw_string.to_string_lossy().to_string(); - char_map.entry(string).or_insert(key_record); - } + let _reset = unsafe { XmbResetIC(*input_context) }; + unsafe { XFree(_reset as *mut c_void) }; } } - (char_map, sym_map) + Ok(deadkeys) } fn convert_to_record_array(&self, syms: &[KeySym]) -> Result> { @@ -202,12 +359,12 @@ impl X11Injector { // Render the state by applying the modifiers for (mod_index, modifier) in modifiers_codes.iter().enumerate() { - if modifier.contains(&(record.code as u8)) { + if modifier.contains(&(record.main.code as u8)) { current_state |= 1 << mod_index; } } - current_record.state = current_state; + current_record.main.state = current_state; records.push(current_record); } @@ -223,7 +380,7 @@ impl X11Injector { focused_window } - fn send_key(&self, window: Window, record: &KeyRecord, pressed: bool, delay_us: u32) { + fn send_key(&self, window: Window, record: &KeyPair, pressed: bool, delay_us: u32) { let root_window = unsafe { XDefaultRootWindow(self.display) }; let mut event = XKeyEvent { display: self.display, @@ -269,7 +426,7 @@ impl X11Injector { } } - fn xtest_send_key(&self, record: &KeyRecord, pressed: bool, delay_us: u32) { + fn xtest_send_key(&self, record: &KeyPair, pressed: bool, delay_us: u32) { // If the key requires any modifier, we need to send those events if record.state != 0 { self.xtest_send_modifiers(record.state, pressed); @@ -345,11 +502,21 @@ impl Injector for X11Injector { for record in records? { if options.disable_fast_inject { - self.xtest_send_key(&record, true, delay_us); - self.xtest_send_key(&record, false, delay_us); + if let Some(deadkey) = &record.preceding_dead_key { + self.xtest_send_key(deadkey, true, delay_us); + self.xtest_send_key(deadkey, false, delay_us); + } + + self.xtest_send_key(&record.main, true, delay_us); + self.xtest_send_key(&record.main, false, delay_us); } else { - self.send_key(focused_window, &record, true, delay_us); - self.send_key(focused_window, &record, false, delay_us); + if let Some(deadkey) = &record.preceding_dead_key { + self.send_key(focused_window, deadkey, true, delay_us); + self.send_key(focused_window, deadkey, false, delay_us); + } + + self.send_key(focused_window, &record.main, true, delay_us); + self.send_key(focused_window, &record.main, false, delay_us); } } @@ -371,11 +538,11 @@ impl Injector for X11Injector { for record in records { if options.disable_fast_inject { - self.xtest_send_key(&record, true, delay_us); - self.xtest_send_key(&record, false, delay_us); + self.xtest_send_key(&record.main, true, delay_us); + self.xtest_send_key(&record.main, false, delay_us); } else { - self.send_key(focused_window, &record, true, delay_us); - self.send_key(focused_window, &record, false, delay_us); + self.send_key(focused_window, &record.main, true, delay_us); + self.send_key(focused_window, &record.main, false, delay_us); } } @@ -401,18 +568,18 @@ impl Injector for X11Injector { // First press the keys for record in records.iter() { if options.disable_fast_inject { - self.xtest_send_key(record, true, delay_us); + self.xtest_send_key(&record.main, true, delay_us); } else { - self.send_key(focused_window, record, true, delay_us); + self.send_key(focused_window, &record.main, true, delay_us); } } // Then release them for record in records.iter().rev() { if options.disable_fast_inject { - self.xtest_send_key(record, false, delay_us); + self.xtest_send_key(&record.main, false, delay_us); } else { - self.send_key(focused_window, record, false, delay_us); + self.send_key(focused_window, &record.main, false, delay_us); } } diff --git a/espanso-modulo/build.rs b/espanso-modulo/build.rs index 53a995b..f3f1a21 100644 --- a/espanso-modulo/build.rs +++ b/espanso-modulo/build.rs @@ -121,6 +121,8 @@ fn build_native() { .file("src/sys/wizard/wizard_gui.cpp") .file("src/sys/welcome/welcome.cpp") .file("src/sys/welcome/welcome_gui.cpp") + .file("src/sys/textview/textview.cpp") + .file("src/sys/textview/textview_gui.cpp") .file("src/sys/troubleshooting/troubleshooting.cpp") .file("src/sys/troubleshooting/troubleshooting_gui.cpp") .flag("/EHsc") @@ -258,6 +260,8 @@ fn build_native() { .file("src/sys/wizard/wizard_gui.cpp") .file("src/sys/welcome/welcome.cpp") .file("src/sys/welcome/welcome_gui.cpp") + .file("src/sys/textview/textview.cpp") + .file("src/sys/textview/textview_gui.cpp") .file("src/sys/troubleshooting/troubleshooting.cpp") .file("src/sys/troubleshooting/troubleshooting_gui.cpp") .file("src/sys/common/mac.mm"); @@ -452,6 +456,8 @@ fn build_native() { .file("src/sys/wizard/wizard_gui.cpp") .file("src/sys/welcome/welcome.cpp") .file("src/sys/welcome/welcome_gui.cpp") + .file("src/sys/textview/textview.cpp") + .file("src/sys/textview/textview_gui.cpp") .file("src/sys/troubleshooting/troubleshooting.cpp") .file("src/sys/troubleshooting/troubleshooting_gui.cpp"); build.flag("-std=c++17"); diff --git a/espanso-modulo/src/lib.rs b/espanso-modulo/src/lib.rs index afd5396..59ce495 100644 --- a/espanso-modulo/src/lib.rs +++ b/espanso-modulo/src/lib.rs @@ -23,6 +23,7 @@ extern crate lazy_static; pub mod form; pub mod search; mod sys; +pub mod textview; pub mod troubleshooting; pub mod welcome; pub mod wizard; diff --git a/espanso-modulo/src/sys/interop/interop.h b/espanso-modulo/src/sys/interop/interop.h index 2d97c1e..a9510fe 100644 --- a/espanso-modulo/src/sys/interop/interop.h +++ b/espanso-modulo/src/sys/interop/interop.h @@ -168,3 +168,11 @@ typedef struct TroubleshootingMetadata { int (*dont_show_again_changed)(int); int (*open_file)(const char * file_name); } TroubleshootingMetadata; + +// TextView + +typedef struct TextViewMetadata { + const char *window_icon_path; + const char *title; + const char *content; +} TextViewMetadata; diff --git a/espanso-modulo/src/sys/interop/mod.rs b/espanso-modulo/src/sys/interop/mod.rs index debd2b9..e93c878 100644 --- a/espanso-modulo/src/sys/interop/mod.rs +++ b/espanso-modulo/src/sys/interop/mod.rs @@ -189,6 +189,14 @@ pub struct ErrorMetadata { pub message: *const c_char, } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct TextViewMetadata { + pub window_icon_path: *const c_char, + pub title: *const c_char, + pub content: *const c_char, +} + // Native bindings #[allow(improper_ctypes)] @@ -220,4 +228,7 @@ extern "C" { // TROUBLESHOOTING pub(crate) fn interop_show_troubleshooting(metadata: *const TroubleshootingMetadata); + + // TEXTVIEW + pub(crate) fn interop_show_text_view(metadata: *const TextViewMetadata); } diff --git a/espanso-modulo/src/sys/mod.rs b/espanso-modulo/src/sys/mod.rs index d69b485..490f3ff 100644 --- a/espanso-modulo/src/sys/mod.rs +++ b/espanso-modulo/src/sys/mod.rs @@ -19,6 +19,7 @@ pub mod form; pub mod search; +pub mod textview; pub mod troubleshooting; pub mod welcome; pub mod wizard; diff --git a/espanso-modulo/src/sys/textview/mod.rs b/espanso-modulo/src/sys/textview/mod.rs new file mode 100644 index 0000000..a3213a1 --- /dev/null +++ b/espanso-modulo/src/sys/textview/mod.rs @@ -0,0 +1,40 @@ +/* + * This file is part of modulo. + * + * Copyright (C) 2020-2021 Federico Terzi + * + * modulo 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. + * + * modulo 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 modulo. If not, see . + */ + +use std::ffi::CString; + +use crate::sys::util::convert_to_cstring_or_null; +use crate::{sys::interop::TextViewMetadata, textview::TextViewOptions}; + +pub fn show(options: TextViewOptions) { + let (_c_window_icon_path, c_window_icon_path_ptr) = + convert_to_cstring_or_null(options.window_icon_path); + let c_title = CString::new(options.title).expect("unable to convert title to CString"); + let c_content = CString::new(options.content).expect("unable to convert content to CString"); + + let textview_metadata = TextViewMetadata { + window_icon_path: c_window_icon_path_ptr, + title: c_title.as_ptr(), + content: c_content.as_ptr(), + }; + + unsafe { + super::interop::interop_show_text_view(&textview_metadata); + } +} diff --git a/espanso-modulo/src/sys/textview/textview.cpp b/espanso-modulo/src/sys/textview/textview.cpp new file mode 100644 index 0000000..47a65ce --- /dev/null +++ b/espanso-modulo/src/sys/textview/textview.cpp @@ -0,0 +1,104 @@ +/* + * This file is part of modulo. + * + * Copyright (C) 2020-2021 Federico Terzi + * + * modulo 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. + * + * modulo 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 modulo. If not, see . + */ + +#define _UNICODE + +#include "../common/common.h" +#include "../interop/interop.h" +#include "./textview_gui.h" + +#include +#include +#include +#include + +TextViewMetadata *text_view_metadata = nullptr; + +// App Code + +class TextViewApp : public wxApp +{ +public: + virtual bool OnInit(); +}; + +class DerivedTextViewFrame : public TextViewFrame +{ +protected: + void on_copy_to_clipboard( wxCommandEvent& event ); + + void on_char_event(wxKeyEvent &event); + +public: + DerivedTextViewFrame(wxWindow *parent); +}; + +DerivedTextViewFrame::DerivedTextViewFrame(wxWindow *parent) + : TextViewFrame(parent) +{ + this->text_content->SetValue(wxString::FromUTF8(text_view_metadata->content)); + this->SetTitle(wxString::FromUTF8(text_view_metadata->title)); + + + Bind(wxEVT_CHAR_HOOK, &DerivedTextViewFrame::on_char_event, this, wxID_ANY); +} + +void DerivedTextViewFrame::on_char_event(wxKeyEvent &event) { + if (event.GetKeyCode() == WXK_ESCAPE) + { + Close(true); + } +} + +void DerivedTextViewFrame::on_copy_to_clipboard( wxCommandEvent& event ) { + if (wxTheClipboard->Open()) + { + wxTheClipboard->SetData( new wxTextDataObject(wxString::FromUTF8(text_view_metadata->content)) ); + wxTheClipboard->Close(); + } +} + +bool TextViewApp::OnInit() +{ + DerivedTextViewFrame *frame = new DerivedTextViewFrame(NULL); + + if (text_view_metadata->window_icon_path) + { + setFrameIcon(wxString::FromUTF8(text_view_metadata->window_icon_path), frame); + } + + frame->Show(true); + Activate(frame); + + return true; +} + +extern "C" void interop_show_text_view(TextViewMetadata *_metadata) +{ +// Setup high DPI support on Windows +#ifdef __WXMSW__ + SetProcessDPIAware(); +#endif + + text_view_metadata = _metadata; + + wxApp::SetInstance(new TextViewApp()); + int argc = 0; + wxEntry(argc, (char **)nullptr); +} \ No newline at end of file diff --git a/espanso-modulo/src/sys/textview/textview.fbp b/espanso-modulo/src/sys/textview/textview.fbp new file mode 100644 index 0000000..72bdce5 --- /dev/null +++ b/espanso-modulo/src/sys/textview/textview.fbp @@ -0,0 +1,223 @@ + + + + + ; + C++ + 1 + source_name + 0 + 0 + res + UTF-8 + connect + textview_gui + 1000 + none + + 0 + TextView + + . + #define _UNICODE + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + wxSYS_COLOUR_WINDOW + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + + TextViewFrame + + 895,545 + wxDEFAULT_FRAME_STYLE + ; ; forward_declare + TextView + + + + wxTAB_TRAVERSAL + 1 + + + bSizer1 + wxVERTICAL + none + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + ,90,90,-1,76,0 + 0 + 0 + wxID_ANY + + 0 + + + + 0 + + 1 + text_content + 1 + + + protected + 1 + + Resizable + 1 + + wxTE_MULTILINE|wxTE_READONLY + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + 10 + wxEXPAND + 0 + + + bSizer2 + wxHORIZONTAL + none + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + + 10 + wxALIGN_CENTER_VERTICAL|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + + 1 + 0 + 1 + + 1 + + 1 + 0 + + Dock + 0 + Left + 1 + + 1 + + + 0 + 0 + wxID_ANY + Copy to Clipboard + + 0 + + 0 + + + 0 + + 1 + copy_to_clipboard_btn + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + on_copy_to_clipboard + + + + + + + + diff --git a/espanso-modulo/src/sys/textview/textview_gui.cpp b/espanso-modulo/src/sys/textview/textview_gui.cpp new file mode 100644 index 0000000..60d71ea --- /dev/null +++ b/espanso-modulo/src/sys/textview/textview_gui.cpp @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#define _UNICODE + +#include "textview_gui.h" + +/////////////////////////////////////////////////////////////////////////// + +TextViewFrame::TextViewFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + wxBoxSizer* bSizer1; + bSizer1 = new wxBoxSizer( wxVERTICAL ); + + text_content = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY ); + text_content->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); + + bSizer1->Add( text_content, 1, wxALL|wxEXPAND, 5 ); + + wxBoxSizer* bSizer2; + bSizer2 = new wxBoxSizer( wxHORIZONTAL ); + + + bSizer2->Add( 0, 0, 1, wxEXPAND, 5 ); + + copy_to_clipboard_btn = new wxButton( this, wxID_ANY, wxT("Copy to Clipboard"), wxDefaultPosition, wxDefaultSize, 0 ); + + copy_to_clipboard_btn->SetDefault(); + bSizer2->Add( copy_to_clipboard_btn, 0, wxALIGN_CENTER_VERTICAL|wxALL, 10 ); + + + bSizer1->Add( bSizer2, 0, wxEXPAND, 10 ); + + + this->SetSizer( bSizer1 ); + this->Layout(); + + this->Centre( wxBOTH ); + + // Connect Events + copy_to_clipboard_btn->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( TextViewFrame::on_copy_to_clipboard ), NULL, this ); +} + +TextViewFrame::~TextViewFrame() +{ + // Disconnect Events + copy_to_clipboard_btn->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( TextViewFrame::on_copy_to_clipboard ), NULL, this ); + +} diff --git a/espanso-modulo/src/sys/textview/textview_gui.h b/espanso-modulo/src/sys/textview/textview_gui.h new file mode 100644 index 0000000..7c6bfc4 --- /dev/null +++ b/espanso-modulo/src/sys/textview/textview_gui.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class TextViewFrame +/////////////////////////////////////////////////////////////////////////////// +class TextViewFrame : public wxFrame +{ + private: + + protected: + wxTextCtrl* text_content; + wxButton* copy_to_clipboard_btn; + + // Virtual event handlers, overide them in your derived class + virtual void on_copy_to_clipboard( wxCommandEvent& event ) { event.Skip(); } + + + public: + + TextViewFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("TextView"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 895,545 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL ); + + ~TextViewFrame(); + +}; + diff --git a/espanso-modulo/src/sys/wizard/wizard.fbp b/espanso-modulo/src/sys/wizard/wizard.fbp index 30500df..ea78a06 100644 --- a/espanso-modulo/src/sys/wizard/wizard.fbp +++ b/espanso-modulo/src/sys/wizard/wizard.fbp @@ -45,8 +45,8 @@ WizardFrame - 550,577 - wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU + 600,577 + wxCAPTION|wxCLOSE_BOX|wxRESIZE_BORDER|wxSYSTEM_MENU ; ; forward_declare Espanso @@ -180,268 +180,333 @@ wxTAB_TRAVERSAL - bSizer2 + bSizer13 wxVERTICAL none - - 0 - wxALIGN_CENTER|wxALL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 - 256,256 - 1 - welcome_image - 1 - - - protected - 1 - - Resizable - 1 - 256,256 - ; ; forward_declare - 0 - - - - - - - - 20 - wxALIGN_CENTER_HORIZONTAL|wxTOP - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - ,90,92,18,70,0 - 0 - 0 - wxID_ANY - Welcome to Espanso! - 0 - - 0 - - - 0 - - 1 - welcome_title_text - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - -1 - - 5 - wxALIGN_CENTER_HORIZONTAL|wxALL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - (version 1.2.3) - 0 - - 0 - - - 0 - - 1 - welcome_version_text - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - -1 - - - - 5 - - 0 - - 20 - protected - 0 - - - - 10 - wxALL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - This wizard will help you to quickly get started with espanso. Click "Start" when you are ready - 0 - - 0 - - - 0 - - 1 - welcome_description_text - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - -1 - - - - 5 - wxEXPAND + wxEXPAND | wxALL 1 - - 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_scrolledWindow2 + 1 + + protected - 0 + 1 + + Resizable + 5 + 5 + 1 + + ; ; forward_declare + 0 + + + + wxHSCROLL|wxVSCROLL + + + bSizer2 + wxVERTICAL + none + + 0 + wxALIGN_CENTER|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + 256,256 + 1 + welcome_image + 1 + + + protected + 1 + + Resizable + 1 + 256,256 + ; ; forward_declare + 0 + + + + + + + + 20 + wxALIGN_CENTER_HORIZONTAL|wxTOP + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + ,90,92,18,70,0 + 0 + 0 + wxID_ANY + Welcome to Espanso! + 0 + + 0 + + + 0 + + 1 + welcome_title_text + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALIGN_CENTER_HORIZONTAL|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + (version 1.2.3) + 0 + + 0 + + + 0 + + 1 + welcome_version_text + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + + 0 + + 20 + protected + 0 + + + + 10 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + This wizard will help you to quickly get started with espanso. Click "Start" when you are ready + 0 + + 0 + + + 0 + + 1 + welcome_description_text + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + @@ -1593,7 +1658,7 @@ wxTAB_TRAVERSAL - bSizer211 + bSizer16 wxVERTICAL none @@ -1659,19 +1724,9 @@ 5 - - 0 - - 20 - protected - 0 - - - - 10 - wxLEFT|wxRIGHT|wxTOP + wxEXPAND | wxALL 1 - + 1 1 1 @@ -1699,8 +1754,6 @@ 0 0 wxID_ANY - The new version uses a slightly different configuration format that powers some exciting new features. To ease the transition, espanso offers two possible choices: - Automatically backup the old configuration in the Documents folder and migrate to the new format (recommended). - Use compatibility mode without changing the configs. Keep in mind that: - Compatibility mode does not support all new espanso features - You can always migrate the configs later For more information, see: - 0 0 @@ -1708,7 +1761,7 @@ 0 1 - migrate_description + m_scrolledWindow4 1 @@ -1716,95 +1769,162 @@ 1 Resizable + 5 + 5 1 - ; ; forward_declare 0 - - 500 + wxVSCROLL + + + bSizer211 + wxVERTICAL + none + + 5 + + 0 + + 20 + protected + 0 + + + + 10 + wxLEFT|wxRIGHT|wxTOP + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + The new version uses a slightly different configuration format that powers some exciting new features. To ease the transition, espanso offers two possible choices: - Automatically backup the old configuration in the Documents folder and migrate to the new format (recommended). - Use compatibility mode without changing the configs. Keep in mind that: - Compatibility mode does not support all new espanso features - You can always migrate the configs later For more information, see: + 0 + + 0 + + + 0 + + 1 + migrate_description + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + 500 + + + + 10 + wxLEFT|wxRIGHT + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + + wxID_ANY + https://espanso.org/migration + + 0 + + + 0 + + 1 + migrate_link + + 1 + + + protected + 1 + + Resizable + 1 + + wxHL_DEFAULT_STYLE + ; ; forward_declare + 0 + + https://espanso.org/migration + + + + + + + - 10 - wxLEFT|wxRIGHT + 5 + wxEXPAND 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - - wxID_ANY - https://espanso.org/migration - - 0 - - - 0 - - 1 - migrate_link - - 1 - - - protected - 1 - - Resizable - 1 - - wxHL_DEFAULT_STYLE - ; ; forward_declare - 0 - - https://espanso.org/migration - - - - - - - - 5 - wxEXPAND - 10 - - 0 - protected - 0 - - - - 5 - wxEXPAND - 1 -1,-1 bSizer8 @@ -2027,274 +2147,339 @@ wxTAB_TRAVERSAL - bSizer2122 + bSizer18 wxVERTICAL none - - 20 - wxALIGN_CENTER_HORIZONTAL|wxALIGN_LEFT|wxTOP - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - ,90,92,18,70,0 - 0 - 0 - wxID_ANY - Launch on System startup - 0 - - 0 - - - 0 - - 1 - auto_start_title - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - -1 - - 5 - - 0 - - 20 - protected - 0 - - - - 10 - wxLEFT|wxRIGHT|wxTOP - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - Espanso can be launched automatically when you start your PC. Do you want to proceed? - 0 - - 0 - - - 0 - - 1 - auto_start_description - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - 500 - - - - 20 - wxALL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - Yes, launch Espanso on system startup (recommended) - - 0 - - - 0 - - 1 - auto_start_checkbox - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - - - - 10 - wxALL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - Note: you can always disable this option later. - 0 - - 0 - - - 0 - - 1 - auto_start_note - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - 500 - - - - 5 - wxEXPAND + wxEXPAND | wxALL 1 - - 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_scrolledWindow6 + 1 + + protected - 0 + 1 + + Resizable + 5 + 5 + 1 + + ; ; forward_declare + 0 + + + + wxHSCROLL|wxVSCROLL + + + bSizer2122 + wxVERTICAL + none + + 20 + wxALIGN_CENTER_HORIZONTAL|wxALIGN_LEFT|wxTOP + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + ,90,92,18,70,0 + 0 + 0 + wxID_ANY + Launch on System startup + 0 + + 0 + + + 0 + + 1 + auto_start_title + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + + 0 + + 20 + protected + 0 + + + + 10 + wxLEFT|wxRIGHT|wxTOP + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Espanso can be launched automatically when you start your PC. Do you want to proceed? + 0 + + 0 + + + 0 + + 1 + auto_start_description + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + 500 + + + + 20 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Yes, launch Espanso on system startup (recommended) + + 0 + + + 0 + + 1 + auto_start_checkbox + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + 10 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Note: you can always disable this option later. + 0 + + 0 + + + 0 + + 1 + auto_start_note + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + 500 + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + @@ -2429,274 +2614,339 @@ wxTAB_TRAVERSAL - bSizer212 + bSizer20 wxVERTICAL none - - 20 - wxALIGN_CENTER_HORIZONTAL|wxALIGN_LEFT|wxTOP - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - ,90,92,18,70,0 - 0 - 0 - wxID_ANY - Add to PATH - 0 - - 0 - - - 0 - - 1 - add_path_title - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - -1 - - 5 - - 0 - - 20 - protected - 0 - - - - 10 - wxLEFT|wxRIGHT|wxTOP - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - Espanso offers a rich CLI interface that enables some powerful features and comes handy when debugging configuration problems. To be easily accessed, espanso can be added to the PATH environment variable automatically. Do you want to proceed? - 0 - - 0 - - - 0 - - 1 - add_path_description - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - 500 - - - - 20 - wxALL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - Yes, add espanso to PATH - - 0 - - - 0 - - 1 - add_path_checkbox - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - - - - 10 - wxALL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - Note: if you don't know what the PATH env variable is, you should probably keep this checked. - 0 - - 0 - - - 0 - - 1 - add_path_note - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - 500 - - - - 5 - wxEXPAND + wxEXPAND | wxALL 1 - - 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_scrolledWindow8 + 1 + + protected - 0 + 1 + + Resizable + 5 + 5 + 1 + + ; ; forward_declare + 0 + + + + wxHSCROLL|wxVSCROLL + + + bSizer212 + wxVERTICAL + none + + 20 + wxALIGN_CENTER_HORIZONTAL|wxALIGN_LEFT|wxTOP + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + ,90,92,18,70,0 + 0 + 0 + wxID_ANY + Add to PATH + 0 + + 0 + + + 0 + + 1 + add_path_title + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + + 0 + + 20 + protected + 0 + + + + 10 + wxLEFT|wxRIGHT|wxTOP + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Espanso offers a rich CLI interface that enables some powerful features and comes handy when debugging configuration problems. To be easily accessed, espanso can be added to the PATH environment variable automatically. Do you want to proceed? + 0 + + 0 + + + 0 + + 1 + add_path_description + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + 500 + + + + 20 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Yes, add espanso to PATH + + 0 + + + 0 + + 1 + add_path_checkbox + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + 10 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Note: if you don't know what the PATH env variable is, you should probably keep this checked. + 0 + + 0 + + + 0 + + 1 + add_path_note + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + 500 + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + diff --git a/espanso-modulo/src/sys/wizard/wizard_gui.cpp b/espanso-modulo/src/sys/wizard/wizard_gui.cpp index b5bbe8e..f9744c5 100644 --- a/espanso-modulo/src/sys/wizard/wizard_gui.cpp +++ b/espanso-modulo/src/sys/wizard/wizard_gui.cpp @@ -26,43 +26,54 @@ WizardFrame::WizardFrame( wxWindow* parent, wxWindowID id, const wxString& title welcome_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); welcome_panel->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxBoxSizer* bSizer13; + bSizer13 = new wxBoxSizer( wxVERTICAL ); + + m_scrolledWindow2 = new wxScrolledWindow( welcome_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_scrolledWindow2->SetScrollRate( 5, 5 ); wxBoxSizer* bSizer2; bSizer2 = new wxBoxSizer( wxVERTICAL ); - welcome_image = new wxStaticBitmap( welcome_panel, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 256,256 ), 0 ); + welcome_image = new wxStaticBitmap( m_scrolledWindow2, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 256,256 ), 0 ); welcome_image->SetMinSize( wxSize( 256,256 ) ); bSizer2->Add( welcome_image, 0, wxALIGN_CENTER|wxALL, 0 ); - welcome_title_text = new wxStaticText( welcome_panel, wxID_ANY, wxT("Welcome to Espanso!"), wxDefaultPosition, wxDefaultSize, 0 ); + welcome_title_text = new wxStaticText( m_scrolledWindow2, wxID_ANY, wxT("Welcome to Espanso!"), wxDefaultPosition, wxDefaultSize, 0 ); welcome_title_text->Wrap( -1 ); welcome_title_text->SetFont( wxFont( 18, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); bSizer2->Add( welcome_title_text, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP, 20 ); - welcome_version_text = new wxStaticText( welcome_panel, wxID_ANY, wxT("(version 1.2.3)"), wxDefaultPosition, wxDefaultSize, 0 ); + welcome_version_text = new wxStaticText( m_scrolledWindow2, wxID_ANY, wxT("(version 1.2.3)"), wxDefaultPosition, wxDefaultSize, 0 ); welcome_version_text->Wrap( -1 ); bSizer2->Add( welcome_version_text, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 ); bSizer2->Add( 0, 20, 0, 0, 5 ); - welcome_description_text = new wxStaticText( welcome_panel, wxID_ANY, wxT("This wizard will help you to quickly get started with espanso. \n\nClick \"Start\" when you are ready"), wxDefaultPosition, wxDefaultSize, 0 ); + welcome_description_text = new wxStaticText( m_scrolledWindow2, wxID_ANY, wxT("This wizard will help you to quickly get started with espanso. \n\nClick \"Start\" when you are ready"), wxDefaultPosition, wxDefaultSize, 0 ); welcome_description_text->Wrap( -1 ); bSizer2->Add( welcome_description_text, 0, wxALL, 10 ); bSizer2->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_scrolledWindow2->SetSizer( bSizer2 ); + m_scrolledWindow2->Layout(); + bSizer2->Fit( m_scrolledWindow2 ); + bSizer13->Add( m_scrolledWindow2, 1, wxEXPAND | wxALL, 5 ); + welcome_start_button = new wxButton( welcome_panel, wxID_ANY, wxT("Start"), wxDefaultPosition, wxDefaultSize, 0 ); welcome_start_button->SetDefault(); - bSizer2->Add( welcome_start_button, 0, wxALIGN_RIGHT|wxALL, 10 ); + bSizer13->Add( welcome_start_button, 0, wxALIGN_RIGHT|wxALL, 10 ); - welcome_panel->SetSizer( bSizer2 ); + welcome_panel->SetSizer( bSizer13 ); welcome_panel->Layout(); - bSizer2->Fit( welcome_panel ); + bSizer13->Fit( welcome_panel ); m_simplebook->AddPage( welcome_panel, wxT("a page"), false ); move_bundle_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); move_bundle_panel->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -175,27 +186,35 @@ WizardFrame::WizardFrame( wxWindow* parent, wxWindowID id, const wxString& title migrate_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); migrate_panel->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - wxBoxSizer* bSizer211; - bSizer211 = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* bSizer16; + bSizer16 = new wxBoxSizer( wxVERTICAL ); migrate_title = new wxStaticText( migrate_panel, wxID_ANY, wxT("Migrate configuration"), wxDefaultPosition, wxDefaultSize, 0 ); migrate_title->Wrap( -1 ); migrate_title->SetFont( wxFont( 18, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); - bSizer211->Add( migrate_title, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_LEFT|wxTOP, 20 ); + bSizer16->Add( migrate_title, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_LEFT|wxTOP, 20 ); + + m_scrolledWindow4 = new wxScrolledWindow( migrate_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL ); + m_scrolledWindow4->SetScrollRate( 5, 5 ); + wxBoxSizer* bSizer211; + bSizer211 = new wxBoxSizer( wxVERTICAL ); bSizer211->Add( 0, 20, 0, 0, 5 ); - migrate_description = new wxStaticText( migrate_panel, wxID_ANY, wxT("The new version uses a slightly different configuration format that powers some exciting new features.\n\nTo ease the transition, espanso offers two possible choices: \n\n - Automatically backup the old configuration in the Documents folder and migrate to the new format (recommended). \n - Use compatibility mode without changing the configs. \n\nKeep in mind that: \n\n - Compatibility mode does not support all new espanso features \n - You can always migrate the configs later \n\nFor more information, see: "), wxDefaultPosition, wxDefaultSize, 0 ); + migrate_description = new wxStaticText( m_scrolledWindow4, wxID_ANY, wxT("The new version uses a slightly different configuration format that powers some exciting new features.\n\nTo ease the transition, espanso offers two possible choices: \n\n - Automatically backup the old configuration in the Documents folder and migrate to the new format (recommended). \n - Use compatibility mode without changing the configs. \n\nKeep in mind that: \n\n - Compatibility mode does not support all new espanso features \n - You can always migrate the configs later \n\nFor more information, see: "), wxDefaultPosition, wxDefaultSize, 0 ); migrate_description->Wrap( 500 ); - bSizer211->Add( migrate_description, 1, wxLEFT|wxRIGHT|wxTOP, 10 ); + bSizer211->Add( migrate_description, 0, wxLEFT|wxRIGHT|wxTOP, 10 ); - migrate_link = new wxHyperlinkCtrl( migrate_panel, wxID_ANY, wxT("https://espanso.org/migration"), wxT("https://espanso.org/migration"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + migrate_link = new wxHyperlinkCtrl( m_scrolledWindow4, wxID_ANY, wxT("https://espanso.org/migration"), wxT("https://espanso.org/migration"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); bSizer211->Add( migrate_link, 0, wxLEFT|wxRIGHT, 10 ); - bSizer211->Add( 0, 0, 10, wxEXPAND, 5 ); + m_scrolledWindow4->SetSizer( bSizer211 ); + m_scrolledWindow4->Layout(); + bSizer211->Fit( m_scrolledWindow4 ); + bSizer16->Add( m_scrolledWindow4, 1, wxEXPAND | wxALL, 5 ); wxBoxSizer* bSizer8; bSizer8 = new wxBoxSizer( wxHORIZONTAL ); @@ -212,20 +231,25 @@ WizardFrame::WizardFrame( wxWindow* parent, wxWindowID id, const wxString& title bSizer8->Add( migrate_backup_and_migrate_button, 0, wxALL, 10 ); - bSizer211->Add( bSizer8, 1, wxEXPAND, 5 ); + bSizer16->Add( bSizer8, 0, wxEXPAND, 5 ); - migrate_panel->SetSizer( bSizer211 ); + migrate_panel->SetSizer( bSizer16 ); migrate_panel->Layout(); - bSizer211->Fit( migrate_panel ); + bSizer16->Fit( migrate_panel ); m_simplebook->AddPage( migrate_panel, wxT("a page"), false ); auto_start_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); auto_start_panel->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxBoxSizer* bSizer18; + bSizer18 = new wxBoxSizer( wxVERTICAL ); + + m_scrolledWindow6 = new wxScrolledWindow( auto_start_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_scrolledWindow6->SetScrollRate( 5, 5 ); wxBoxSizer* bSizer2122; bSizer2122 = new wxBoxSizer( wxVERTICAL ); - auto_start_title = new wxStaticText( auto_start_panel, wxID_ANY, wxT("Launch on System startup"), wxDefaultPosition, wxDefaultSize, 0 ); + auto_start_title = new wxStaticText( m_scrolledWindow6, wxID_ANY, wxT("Launch on System startup"), wxDefaultPosition, wxDefaultSize, 0 ); auto_start_title->Wrap( -1 ); auto_start_title->SetFont( wxFont( 18, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); @@ -234,38 +258,49 @@ WizardFrame::WizardFrame( wxWindow* parent, wxWindowID id, const wxString& title bSizer2122->Add( 0, 20, 0, 0, 5 ); - auto_start_description = new wxStaticText( auto_start_panel, wxID_ANY, wxT("Espanso can be launched automatically when you start your PC. \n\nDo you want to proceed?"), wxDefaultPosition, wxDefaultSize, 0 ); + auto_start_description = new wxStaticText( m_scrolledWindow6, wxID_ANY, wxT("Espanso can be launched automatically when you start your PC. \n\nDo you want to proceed?"), wxDefaultPosition, wxDefaultSize, 0 ); auto_start_description->Wrap( 500 ); bSizer2122->Add( auto_start_description, 0, wxLEFT|wxRIGHT|wxTOP, 10 ); - auto_start_checkbox = new wxCheckBox( auto_start_panel, wxID_ANY, wxT("Yes, launch Espanso on system startup (recommended)"), wxDefaultPosition, wxDefaultSize, 0 ); + auto_start_checkbox = new wxCheckBox( m_scrolledWindow6, wxID_ANY, wxT("Yes, launch Espanso on system startup (recommended)"), wxDefaultPosition, wxDefaultSize, 0 ); auto_start_checkbox->SetValue(true); bSizer2122->Add( auto_start_checkbox, 0, wxALL, 20 ); - auto_start_note = new wxStaticText( auto_start_panel, wxID_ANY, wxT("Note: you can always disable this option later."), wxDefaultPosition, wxDefaultSize, 0 ); + auto_start_note = new wxStaticText( m_scrolledWindow6, wxID_ANY, wxT("Note: you can always disable this option later."), wxDefaultPosition, wxDefaultSize, 0 ); auto_start_note->Wrap( 500 ); bSizer2122->Add( auto_start_note, 0, wxALL, 10 ); bSizer2122->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_scrolledWindow6->SetSizer( bSizer2122 ); + m_scrolledWindow6->Layout(); + bSizer2122->Fit( m_scrolledWindow6 ); + bSizer18->Add( m_scrolledWindow6, 1, wxEXPAND | wxALL, 5 ); + auto_start_continue = new wxButton( auto_start_panel, wxID_ANY, wxT("Continue"), wxDefaultPosition, wxDefaultSize, 0 ); auto_start_continue->SetDefault(); - bSizer2122->Add( auto_start_continue, 0, wxALIGN_RIGHT|wxALL, 10 ); + bSizer18->Add( auto_start_continue, 0, wxALIGN_RIGHT|wxALL, 10 ); - auto_start_panel->SetSizer( bSizer2122 ); + auto_start_panel->SetSizer( bSizer18 ); auto_start_panel->Layout(); - bSizer2122->Fit( auto_start_panel ); + bSizer18->Fit( auto_start_panel ); m_simplebook->AddPage( auto_start_panel, wxT("a page"), false ); add_path_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); add_path_panel->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxBoxSizer* bSizer20; + bSizer20 = new wxBoxSizer( wxVERTICAL ); + + m_scrolledWindow8 = new wxScrolledWindow( add_path_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_scrolledWindow8->SetScrollRate( 5, 5 ); wxBoxSizer* bSizer212; bSizer212 = new wxBoxSizer( wxVERTICAL ); - add_path_title = new wxStaticText( add_path_panel, wxID_ANY, wxT("Add to PATH"), wxDefaultPosition, wxDefaultSize, 0 ); + add_path_title = new wxStaticText( m_scrolledWindow8, wxID_ANY, wxT("Add to PATH"), wxDefaultPosition, wxDefaultSize, 0 ); add_path_title->Wrap( -1 ); add_path_title->SetFont( wxFont( 18, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); @@ -274,30 +309,36 @@ WizardFrame::WizardFrame( wxWindow* parent, wxWindowID id, const wxString& title bSizer212->Add( 0, 20, 0, 0, 5 ); - add_path_description = new wxStaticText( add_path_panel, wxID_ANY, wxT("Espanso offers a rich CLI interface that enables some powerful features and comes handy when debugging configuration problems.\n\nTo be easily accessed, espanso can be added to the PATH environment variable automatically. Do you want to proceed?\n"), wxDefaultPosition, wxDefaultSize, 0 ); + add_path_description = new wxStaticText( m_scrolledWindow8, wxID_ANY, wxT("Espanso offers a rich CLI interface that enables some powerful features and comes handy when debugging configuration problems.\n\nTo be easily accessed, espanso can be added to the PATH environment variable automatically. Do you want to proceed?\n"), wxDefaultPosition, wxDefaultSize, 0 ); add_path_description->Wrap( 500 ); bSizer212->Add( add_path_description, 0, wxLEFT|wxRIGHT|wxTOP, 10 ); - add_path_checkbox = new wxCheckBox( add_path_panel, wxID_ANY, wxT("Yes, add espanso to PATH"), wxDefaultPosition, wxDefaultSize, 0 ); + add_path_checkbox = new wxCheckBox( m_scrolledWindow8, wxID_ANY, wxT("Yes, add espanso to PATH"), wxDefaultPosition, wxDefaultSize, 0 ); add_path_checkbox->SetValue(true); bSizer212->Add( add_path_checkbox, 0, wxALL, 20 ); - add_path_note = new wxStaticText( add_path_panel, wxID_ANY, wxT("Note: if you don't know what the PATH env variable is, you should probably keep this checked."), wxDefaultPosition, wxDefaultSize, 0 ); + add_path_note = new wxStaticText( m_scrolledWindow8, wxID_ANY, wxT("Note: if you don't know what the PATH env variable is, you should probably keep this checked."), wxDefaultPosition, wxDefaultSize, 0 ); add_path_note->Wrap( 500 ); bSizer212->Add( add_path_note, 0, wxALL, 10 ); bSizer212->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_scrolledWindow8->SetSizer( bSizer212 ); + m_scrolledWindow8->Layout(); + bSizer212->Fit( m_scrolledWindow8 ); + bSizer20->Add( m_scrolledWindow8, 1, wxEXPAND | wxALL, 5 ); + add_path_continue_button = new wxButton( add_path_panel, wxID_ANY, wxT("Continue"), wxDefaultPosition, wxDefaultSize, 0 ); add_path_continue_button->SetDefault(); - bSizer212->Add( add_path_continue_button, 0, wxALIGN_RIGHT|wxALL, 10 ); + bSizer20->Add( add_path_continue_button, 0, wxALIGN_RIGHT|wxALL, 10 ); - add_path_panel->SetSizer( bSizer212 ); + add_path_panel->SetSizer( bSizer20 ); add_path_panel->Layout(); - bSizer212->Fit( add_path_panel ); + bSizer20->Fit( add_path_panel ); m_simplebook->AddPage( add_path_panel, wxT("a page"), false ); accessibility_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); accessibility_panel->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); diff --git a/espanso-modulo/src/sys/wizard/wizard_gui.h b/espanso-modulo/src/sys/wizard/wizard_gui.h index 1a9e24a..1ae7957 100644 --- a/espanso-modulo/src/sys/wizard/wizard_gui.h +++ b/espanso-modulo/src/sys/wizard/wizard_gui.h @@ -20,12 +20,12 @@ #include #include #include -#include #include +#include +#include #include #include #include -#include #include #include @@ -43,6 +43,7 @@ class WizardFrame : public wxFrame wxTimer check_timer; wxSimplebook* m_simplebook; wxPanel* welcome_panel; + wxScrolledWindow* m_scrolledWindow2; wxStaticBitmap* welcome_image; wxStaticText* welcome_title_text; wxStaticText* welcome_version_text; @@ -65,17 +66,20 @@ class WizardFrame : public wxFrame wxButton* wrong_edition_button; wxPanel* migrate_panel; wxStaticText* migrate_title; + wxScrolledWindow* m_scrolledWindow4; wxStaticText* migrate_description; wxHyperlinkCtrl* migrate_link; wxButton* migrate_compatibility_mode_button; wxButton* migrate_backup_and_migrate_button; wxPanel* auto_start_panel; + wxScrolledWindow* m_scrolledWindow6; wxStaticText* auto_start_title; wxStaticText* auto_start_description; wxCheckBox* auto_start_checkbox; wxStaticText* auto_start_note; wxButton* auto_start_continue; wxPanel* add_path_panel; + wxScrolledWindow* m_scrolledWindow8; wxStaticText* add_path_title; wxStaticText* add_path_description; wxCheckBox* add_path_checkbox; @@ -105,7 +109,7 @@ class WizardFrame : public wxFrame public: - WizardFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Espanso"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 550,577 ), long style = wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU|wxTAB_TRAVERSAL ); + WizardFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Espanso"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 600,577 ), long style = wxCAPTION|wxCLOSE_BOX|wxRESIZE_BORDER|wxSYSTEM_MENU|wxTAB_TRAVERSAL ); ~WizardFrame(); diff --git a/espanso-modulo/src/textview/mod.rs b/espanso-modulo/src/textview/mod.rs new file mode 100644 index 0000000..664ba01 --- /dev/null +++ b/espanso-modulo/src/textview/mod.rs @@ -0,0 +1,26 @@ +/* + * This file is part of modulo. + * + * Copyright (C) 2020-2021 Federico Terzi + * + * modulo 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. + * + * modulo 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 modulo. If not, see . + */ + +pub use crate::sys::textview::show; + +pub struct TextViewOptions { + pub window_icon_path: Option, + pub title: String, + pub content: String, +} diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml index 7248484..4716d8c 100644 --- a/espanso/Cargo.toml +++ b/espanso/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "espanso" -version = "2.1.0-alpha" +version = "2.1.1-alpha" authors = ["Federico Terzi "] license = "GPL-3.0" description = "Cross-platform Text Expander written in Rust" diff --git a/espanso/src/cli/modulo/mod.rs b/espanso/src/cli/modulo/mod.rs index 7954d50..ffdbb6e 100644 --- a/espanso/src/cli/modulo/mod.rs +++ b/espanso/src/cli/modulo/mod.rs @@ -24,6 +24,8 @@ mod form; #[cfg(feature = "modulo")] mod search; #[cfg(feature = "modulo")] +mod textview; +#[cfg(feature = "modulo")] mod troubleshoot; #[cfg(feature = "modulo")] mod welcome; @@ -57,6 +59,10 @@ fn modulo_main(args: CliModuleArgs) -> i32 { return welcome::welcome_main(matches, &paths, &icon_paths); } + if let Some(matches) = cli_args.subcommand_matches("textview") { + return textview::textview_main(matches, &icon_paths); + } + if cli_args.subcommand_matches("troubleshoot").is_some() { return troubleshoot::troubleshoot_main(&paths, &icon_paths); } diff --git a/espanso/src/cli/modulo/textview.rs b/espanso/src/cli/modulo/textview.rs new file mode 100644 index 0000000..229668a --- /dev/null +++ b/espanso/src/cli/modulo/textview.rs @@ -0,0 +1,51 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 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::icon::IconPaths; +use clap::ArgMatches; +use espanso_modulo::textview::TextViewOptions; + +pub fn textview_main(matches: &ArgMatches, icon_paths: &IconPaths) -> i32 { + let title = matches.value_of("title").unwrap_or("Espanso"); + + let input_file = matches + .value_of("input_file") + .expect("missing input, please specify the -i option"); + let data = if input_file == "-" { + use std::io::Read; + let mut buffer = String::new(); + std::io::stdin() + .read_to_string(&mut buffer) + .expect("unable to obtain input from stdin"); + buffer + } else { + std::fs::read_to_string(input_file).expect("unable to read input file") + }; + + espanso_modulo::textview::show(TextViewOptions { + window_icon_path: icon_paths + .wizard_icon + .as_ref() + .map(|path| path.to_string_lossy().to_string()), + title: title.to_string(), + content: data, + }); + + 0 +} diff --git a/espanso/src/cli/worker/builtin/debug.rs b/espanso/src/cli/worker/builtin/debug.rs index 6ab41df..3120dd0 100644 --- a/espanso/src/cli/worker/builtin/debug.rs +++ b/espanso/src/cli/worker/builtin/debug.rs @@ -17,7 +17,7 @@ * along with espanso. If not, see . */ -use espanso_engine::event::{effect::TextInjectRequest, EventType}; +use espanso_engine::event::{effect::TextInjectRequest, ui::ShowTextEvent, EventType}; use crate::cli::worker::builtin::generate_next_builtin_id; @@ -29,7 +29,7 @@ pub fn create_match_paste_active_config_info() -> BuiltInMatch { BuiltInMatch { id: generate_next_builtin_id(), label: "Paste active config information", - triggers: vec!["#acfg#".to_string()], + triggers: vec!["#pacfg#".to_string()], action: |context| { let dump = context.get_active_config().pretty_dump(); @@ -46,7 +46,7 @@ pub fn create_match_paste_active_app_info() -> BuiltInMatch { BuiltInMatch { id: generate_next_builtin_id(), label: "Paste active application information (detect)", - triggers: vec!["#detect#".to_string()], + triggers: vec!["#pdetect#".to_string()], action: |context| { let info = context.get_active_app_info(); @@ -65,3 +65,54 @@ pub fn create_match_paste_active_app_info() -> BuiltInMatch { ..Default::default() } } + +pub fn create_match_show_active_config_info() -> BuiltInMatch { + BuiltInMatch { + id: generate_next_builtin_id(), + label: "Show active config information", + triggers: vec!["#acfg#".to_string()], + action: |context| { + let dump = context.get_active_config().pretty_dump(); + + EventType::ShowText(ShowTextEvent { + text: dump, + title: "Active configuration".to_string(), + }) + }, + ..Default::default() + } +} + +pub fn create_match_show_active_app_info() -> BuiltInMatch { + BuiltInMatch { + id: generate_next_builtin_id(), + label: "Show active application information (detect)", + triggers: vec!["#detect#".to_string()], + action: |context| { + let info = context.get_active_app_info(); + + let dump = format!( + "title: '{}'\nexec: '{}'\nclass: '{}'", + info.title.unwrap_or_default(), + info.exec.unwrap_or_default(), + info.class.unwrap_or_default() + ); + + EventType::ShowText(ShowTextEvent { + text: dump, + title: "Active application information (detect)".to_string(), + }) + }, + ..Default::default() + } +} + +pub fn create_match_show_logs() -> BuiltInMatch { + BuiltInMatch { + id: generate_next_builtin_id(), + label: "Show Espanso's logs", + triggers: vec!["#log#".to_string()], + action: |_| EventType::ShowLogs, + ..Default::default() + } +} diff --git a/espanso/src/cli/worker/builtin/mod.rs b/espanso/src/cli/worker/builtin/mod.rs index 1189f1b..ee8f7f8 100644 --- a/espanso/src/cli/worker/builtin/mod.rs +++ b/espanso/src/cli/worker/builtin/mod.rs @@ -55,6 +55,9 @@ pub fn get_builtin_matches(config: &dyn Config) -> Vec { let mut matches = vec![ debug::create_match_paste_active_config_info(), debug::create_match_paste_active_app_info(), + debug::create_match_show_active_config_info(), + debug::create_match_show_active_app_info(), + debug::create_match_show_logs(), process::create_match_exit(), process::create_match_restart(), ]; diff --git a/espanso/src/cli/worker/config.rs b/espanso/src/cli/worker/config.rs index 408782a..dd69176 100644 --- a/espanso/src/cli/worker/config.rs +++ b/espanso/src/cli/worker/config.rs @@ -25,7 +25,10 @@ use espanso_config::{ }; use espanso_info::{AppInfo, AppInfoProvider}; -use super::builtin::is_builtin_match; +use super::{ + builtin::is_builtin_match, + engine::process::middleware::render::extension::clipboard::ClipboardOperationOptionsProvider, +}; pub struct ConfigManager<'a> { config_store: &'a dyn ConfigStore, @@ -139,6 +142,16 @@ impl<'a> super::engine::dispatch::executor::clipboard_injector::ClipboardParamsP disable_x11_fast_inject: active.disable_x11_fast_inject(), restore_clipboard: active.preserve_clipboard(), restore_clipboard_delay: active.restore_clipboard_delay(), + x11_use_xclip_backend: active.x11_use_xclip_backend(), + } + } +} + +impl<'a> ClipboardOperationOptionsProvider for ConfigManager<'a> { + fn get_operation_options(&self) -> espanso_clipboard::ClipboardOperationOptions { + let active = self.active(); + espanso_clipboard::ClipboardOperationOptions { + use_xclip_backend: active.x11_use_xclip_backend(), } } } diff --git a/espanso/src/cli/worker/engine/dispatch/executor/clipboard_injector.rs b/espanso/src/cli/worker/engine/dispatch/executor/clipboard_injector.rs index 8ebfd54..163245b 100644 --- a/espanso/src/cli/worker/engine/dispatch/executor/clipboard_injector.rs +++ b/espanso/src/cli/worker/engine/dispatch/executor/clipboard_injector.rs @@ -19,7 +19,7 @@ use std::{convert::TryInto, path::PathBuf}; -use espanso_clipboard::Clipboard; +use espanso_clipboard::{Clipboard, ClipboardOperationOptions}; use espanso_inject::{keys::Key, InjectionOptions, Injector}; use log::error; @@ -39,6 +39,7 @@ pub struct ClipboardParams { pub disable_x11_fast_inject: bool, pub restore_clipboard: bool, pub restore_clipboard_delay: usize, + pub x11_use_xclip_backend: bool, } pub struct ClipboardInjectorAdapter<'a> { @@ -103,11 +104,19 @@ impl<'a> ClipboardInjectorAdapter<'a> { Some(ClipboardRestoreGuard::lock( self.clipboard, params.restore_clipboard_delay.try_into().unwrap(), + self.get_operation_options(), )) } else { None } } + + fn get_operation_options(&self) -> ClipboardOperationOptions { + let params = self.params_provider.get(); + ClipboardOperationOptions { + use_xclip_backend: params.x11_use_xclip_backend, + } + } } impl<'a> TextInjector for ClipboardInjectorAdapter<'a> { @@ -118,7 +127,9 @@ impl<'a> TextInjector for ClipboardInjectorAdapter<'a> { fn inject_text(&self, text: &str) -> anyhow::Result<()> { let _guard = self.restore_clipboard_guard(); - self.clipboard.set_text(text)?; + self + .clipboard + .set_text(text, &self.get_operation_options())?; self.send_paste_combination()?; @@ -130,7 +141,9 @@ impl<'a> HtmlInjector for ClipboardInjectorAdapter<'a> { fn inject_html(&self, html: &str, fallback_text: &str) -> anyhow::Result<()> { let _guard = self.restore_clipboard_guard(); - self.clipboard.set_html(html, Some(fallback_text))?; + self + .clipboard + .set_html(html, Some(fallback_text), &self.get_operation_options())?; self.send_paste_combination()?; @@ -153,7 +166,9 @@ impl<'a> ImageInjector for ClipboardInjectorAdapter<'a> { let _guard = self.restore_clipboard_guard(); - self.clipboard.set_image(&path)?; + self + .clipboard + .set_image(&path, &self.get_operation_options())?; self.send_paste_combination()?; @@ -165,16 +180,22 @@ struct ClipboardRestoreGuard<'a> { clipboard: &'a dyn Clipboard, content: Option, restore_delay: u64, + clipboard_operation_options: ClipboardOperationOptions, } impl<'a> ClipboardRestoreGuard<'a> { - pub fn lock(clipboard: &'a dyn Clipboard, restore_delay: u64) -> Self { - let clipboard_content = clipboard.get_text(); + pub fn lock( + clipboard: &'a dyn Clipboard, + restore_delay: u64, + clipboard_operation_options: ClipboardOperationOptions, + ) -> Self { + let clipboard_content = clipboard.get_text(&clipboard_operation_options); Self { clipboard, content: clipboard_content, restore_delay, + clipboard_operation_options, } } } @@ -186,7 +207,10 @@ impl<'a> Drop for ClipboardRestoreGuard<'a> { // A delay is needed to mitigate the problem std::thread::sleep(std::time::Duration::from_millis(self.restore_delay)); - if let Err(error) = self.clipboard.set_text(&content) { + if let Err(error) = self + .clipboard + .set_text(&content, &self.clipboard_operation_options) + { error!( "unable to restore clipboard content after expansion: {}", error diff --git a/espanso/src/cli/worker/engine/dispatch/executor/mod.rs b/espanso/src/cli/worker/engine/dispatch/executor/mod.rs index 7fee77a..ff3dc19 100644 --- a/espanso/src/cli/worker/engine/dispatch/executor/mod.rs +++ b/espanso/src/cli/worker/engine/dispatch/executor/mod.rs @@ -23,6 +23,7 @@ pub mod event_injector; pub mod icon; pub mod key_injector; pub mod secure_input; +pub mod text_ui; pub trait InjectParamsProvider { fn get(&self) -> InjectParams; diff --git a/espanso/src/cli/worker/engine/dispatch/executor/text_ui.rs b/espanso/src/cli/worker/engine/dispatch/executor/text_ui.rs new file mode 100644 index 0000000..488d964 --- /dev/null +++ b/espanso/src/cli/worker/engine/dispatch/executor/text_ui.rs @@ -0,0 +1,48 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 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 espanso_engine::dispatch::TextUIHandler; +use espanso_path::Paths; + +use crate::gui::TextUI; + +pub struct TextUIHandlerAdapter<'a> { + text_ui: &'a dyn TextUI, + paths: &'a Paths, +} + +impl<'a> TextUIHandlerAdapter<'a> { + pub fn new(text_ui: &'a dyn TextUI, paths: &'a Paths) -> Self { + Self { text_ui, paths } + } +} + +impl<'a> TextUIHandler for TextUIHandlerAdapter<'a> { + fn show_text(&self, title: &str, text: &str) -> anyhow::Result<()> { + self.text_ui.show_text(title, text)?; + Ok(()) + } + + fn show_logs(&self) -> anyhow::Result<()> { + self + .text_ui + .show_file("Espanso Logs", &self.paths.runtime.join("espanso.log"))?; + Ok(()) + } +} diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs index 07c653a..513bff5 100644 --- a/espanso/src/cli/worker/engine/mod.rs +++ b/espanso/src/cli/worker/engine/mod.rs @@ -37,6 +37,7 @@ use crate::{ clipboard_injector::ClipboardInjectorAdapter, context_menu::ContextMenuHandlerAdapter, event_injector::EventInjectorAdapter, icon::IconHandlerAdapter, key_injector::KeyInjectorAdapter, secure_input::SecureInputManagerAdapter, + text_ui::TextUIHandlerAdapter, }, process::middleware::{ image_resolve::PathProviderAdapter, @@ -106,6 +107,7 @@ pub fn initialize_and_spawn( let modulo_manager = crate::gui::modulo::manager::ModuloManager::new(); let modulo_form_ui = crate::gui::modulo::form::ModuloFormUI::new(&modulo_manager); let modulo_search_ui = crate::gui::modulo::search::ModuloSearchUI::new(&modulo_manager); + let modulo_text_ui = crate::gui::modulo::textview::ModuloTextUI::new(&modulo_manager); let context: Box = Box::new(super::context::DefaultContext::new( &config_manager, @@ -179,7 +181,7 @@ pub fn initialize_and_spawn( let clipboard = espanso_clipboard::get_clipboard(Default::default()) .expect("failed to initialize clipboard module"); // TODO: handle options - let clipboard_adapter = ClipboardAdapter::new(&*clipboard); + let clipboard_adapter = ClipboardAdapter::new(&*clipboard, &config_manager); let clipboard_extension = espanso_render::extension::clipboard::ClipboardExtension::new(&clipboard_adapter); let date_extension = espanso_render::extension::date::DateExtension::new(); @@ -241,6 +243,7 @@ pub fn initialize_and_spawn( let context_menu_adapter = ContextMenuHandlerAdapter::new(&*ui_remote); let icon_adapter = IconHandlerAdapter::new(&*ui_remote); let secure_input_adapter = SecureInputManagerAdapter::new(); + let text_ui_adapter = TextUIHandlerAdapter::new(&modulo_text_ui, &paths); let dispatcher = espanso_engine::dispatch::default( &event_injector, &clipboard_injector, @@ -251,6 +254,7 @@ pub fn initialize_and_spawn( &context_menu_adapter, &icon_adapter, &secure_input_adapter, + &text_ui_adapter, ); // Disable previously granted linux capabilities if not needed anymore diff --git a/espanso/src/cli/worker/engine/process/middleware/match_select.rs b/espanso/src/cli/worker/engine/process/middleware/match_select.rs index b318bc2..0f0b74e 100644 --- a/espanso/src/cli/worker/engine/process/middleware/match_select.rs +++ b/espanso/src/cli/worker/engine/process/middleware/match_select.rs @@ -55,11 +55,15 @@ impl<'a> MatchSelector for MatchSelectorAdapter<'a> { let search_items: Vec = matches .into_iter() .map(|m| { - let clipped_label = &m.label[..std::cmp::min(m.label.len(), MAX_LABEL_LEN)]; + let clipped_label: String = m + .label + .chars() + .take(std::cmp::min(m.label.len(), MAX_LABEL_LEN)) + .collect(); SearchItem { id: m.id.to_string(), - label: clipped_label.to_string(), + label: clipped_label, tag: m.tag.map(String::from), is_builtin: m.is_builtin, } diff --git a/espanso/src/cli/worker/engine/process/middleware/render/extension/clipboard.rs b/espanso/src/cli/worker/engine/process/middleware/render/extension/clipboard.rs index 57b2c91..999b308 100644 --- a/espanso/src/cli/worker/engine/process/middleware/render/extension/clipboard.rs +++ b/espanso/src/cli/worker/engine/process/middleware/render/extension/clipboard.rs @@ -17,21 +17,36 @@ * along with espanso. If not, see . */ -use espanso_clipboard::Clipboard; +use espanso_clipboard::{Clipboard, ClipboardOperationOptions}; use espanso_render::extension::clipboard::ClipboardProvider; +pub trait ClipboardOperationOptionsProvider { + fn get_operation_options(&self) -> ClipboardOperationOptions; +} + pub struct ClipboardAdapter<'a> { clipboard: &'a dyn Clipboard, + clipboard_operation_options_provider: &'a dyn ClipboardOperationOptionsProvider, } impl<'a> ClipboardAdapter<'a> { - pub fn new(clipboard: &'a dyn Clipboard) -> Self { - Self { clipboard } + pub fn new( + clipboard: &'a dyn Clipboard, + clipboard_operation_options_provider: &'a dyn ClipboardOperationOptionsProvider, + ) -> Self { + Self { + clipboard, + clipboard_operation_options_provider, + } } } impl<'a> ClipboardProvider for ClipboardAdapter<'a> { fn get_text(&self) -> Option { - self.clipboard.get_text() + self.clipboard.get_text( + &self + .clipboard_operation_options_provider + .get_operation_options(), + ) } } diff --git a/espanso/src/gui/mod.rs b/espanso/src/gui/mod.rs index bcd26e7..86ae5db 100644 --- a/espanso/src/gui/mod.rs +++ b/espanso/src/gui/mod.rs @@ -17,7 +17,7 @@ * along with espanso. If not, see . */ -use std::collections::HashMap; +use std::{collections::HashMap, path::Path}; use anyhow::Result; @@ -58,3 +58,8 @@ pub enum FormField { values: Vec, }, } + +pub trait TextUI { + fn show_text(&self, title: &str, text: &str) -> Result<()>; + fn show_file(&self, title: &str, path: &Path) -> Result<()>; +} diff --git a/espanso/src/gui/modulo/manager.rs b/espanso/src/gui/modulo/manager.rs index 0cc3184..9f7ed51 100644 --- a/espanso/src/gui/modulo/manager.rs +++ b/espanso/src/gui/modulo/manager.rs @@ -39,6 +39,40 @@ impl ModuloManager { Self { is_support_enabled } } + pub fn spawn(&self, args: &[&str], body: &str) -> Result<()> { + if self.is_support_enabled { + let exec_path = std::env::current_exe().expect("unable to obtain current exec path"); + let mut command = Command::new(exec_path); + let mut full_args = vec!["modulo"]; + full_args.extend(args); + command + .args(full_args) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + crate::util::set_command_flags(&mut command); + + let child = command.spawn(); + + match child { + Ok(mut child) => { + if let Some(stdin) = child.stdin.as_mut() { + match stdin.write_all(body.as_bytes()) { + Ok(_) => Ok(()), + Err(error) => Err(ModuloError::Error(error).into()), + } + } else { + Err(ModuloError::StdinError.into()) + } + } + Err(error) => Err(ModuloError::Error(error).into()), + } + } else { + Err(ModuloError::MissingModulo.into()) + } + } + pub fn invoke(&self, args: &[&str], body: &str) -> Result { if self.is_support_enabled { let exec_path = std::env::current_exe().expect("unable to obtain current exec path"); diff --git a/espanso/src/gui/modulo/mod.rs b/espanso/src/gui/modulo/mod.rs index 9e27396..e065ab4 100644 --- a/espanso/src/gui/modulo/mod.rs +++ b/espanso/src/gui/modulo/mod.rs @@ -20,3 +20,4 @@ pub mod form; pub mod manager; pub mod search; +pub mod textview; diff --git a/espanso/src/gui/modulo/textview.rs b/espanso/src/gui/modulo/textview.rs new file mode 100644 index 0000000..57c31ba --- /dev/null +++ b/espanso/src/gui/modulo/textview.rs @@ -0,0 +1,51 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 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::gui::TextUI; + +use super::manager::ModuloManager; + +pub struct ModuloTextUI<'a> { + manager: &'a ModuloManager, +} + +impl<'a> ModuloTextUI<'a> { + pub fn new(manager: &'a ModuloManager) -> Self { + Self { manager } + } +} + +impl<'a> TextUI for ModuloTextUI<'a> { + fn show_text(&self, title: &str, text: &str) -> anyhow::Result<()> { + self + .manager + .spawn(&["textview", "--title", title, "-i", "-"], text)?; + + Ok(()) + } + + fn show_file(&self, title: &str, path: &std::path::Path) -> anyhow::Result<()> { + let path_str = path.to_string_lossy().to_string(); + self + .manager + .spawn(&["textview", "--title", title, "-i", &path_str], "")?; + + Ok(()) + } +} diff --git a/espanso/src/main.rs b/espanso/src/main.rs index 58cf84f..97d42d9 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -292,6 +292,23 @@ For example, specifying 'email' is equivalent to 'match/email.yml'."#)) .help("Interpret the input data as JSON"), ), ) + .subcommand( + SubCommand::with_name("textview") + .about("Display a Text View") + .arg( + Arg::with_name("input_file") + .short("i") + .takes_value(true) + .help("Input file or - for stdin"), + ) + .arg( + Arg::with_name("title") + .long("title") + .required(true) + .takes_value(true) + .help("Window title to display"), + ), + ) .subcommand(SubCommand::with_name("troubleshoot").about("Display the troubleshooting GUI")) .subcommand( SubCommand::with_name("welcome") diff --git a/espanso/src/patch/mod.rs b/espanso/src/patch/mod.rs index f1ce634..fa6d065 100644 --- a/espanso/src/patch/mod.rs +++ b/espanso/src/patch/mod.rs @@ -42,6 +42,7 @@ fn get_builtin_patches() -> Vec { return vec![ patches::linux::alacritty_terminal_x11::patch(), patches::linux::emacs_x11::patch(), + patches::linux::gedit_x11::patch(), patches::linux::generic_terminal_x11::patch(), patches::linux::kitty_terminal_x11::patch(), patches::linux::konsole_terminal_x11::patch(), diff --git a/espanso/src/patch/patches/linux/gedit_x11.rs b/espanso/src/patch/patches/linux/gedit_x11.rs new file mode 100644 index 0000000..458f9c9 --- /dev/null +++ b/espanso/src/patch/patches/linux/gedit_x11.rs @@ -0,0 +1,41 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 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 std::sync::Arc; + +use crate::patch::patches::{PatchedConfig, Patches}; +use crate::patch::PatchDefinition; + +pub fn patch() -> PatchDefinition { + PatchDefinition { + name: module_path!().split(':').last().unwrap_or("unknown"), + is_enabled: || cfg!(target_os = "linux") && !super::util::is_wayland(), + should_patch: |app| app.class.unwrap_or_default().contains("Gedit"), + apply: |base, name| { + Arc::new(PatchedConfig::patch( + base, + name, + Patches { + x11_use_xclip_backend: Some(true), + ..Default::default() + }, + )) + }, + } +} diff --git a/espanso/src/patch/patches/linux/mod.rs b/espanso/src/patch/patches/linux/mod.rs index 3d42011..1e0bc94 100644 --- a/espanso/src/patch/patches/linux/mod.rs +++ b/espanso/src/patch/patches/linux/mod.rs @@ -19,6 +19,7 @@ pub mod alacritty_terminal_x11; pub mod emacs_x11; +pub mod gedit_x11; pub mod generic_terminal_x11; pub mod kitty_terminal_x11; pub mod konsole_terminal_x11; diff --git a/espanso/src/patch/patches/mod.rs b/espanso/src/patch/patches/mod.rs index c6dd4f7..e45567d 100644 --- a/espanso/src/patch/patches/mod.rs +++ b/espanso/src/patch/patches/mod.rs @@ -50,5 +50,6 @@ generate_patchable_config!( undo_backspace -> bool, win32_exclude_orphan_events -> bool, win32_keyboard_layout_cache_interval -> i64, + x11_use_xclip_backend -> bool, keyboard_layout -> Option ); diff --git a/snapcraft.yaml b/snapcraft.yaml index ad623ef..8f5437c 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: espanso -version: 2.1.0-alpha +version: 2.1.1-alpha summary: A Cross-platform Text Expander written in Rust description: | espanso is a Cross-platform, Text Expander written in Rust. @@ -80,6 +80,7 @@ parts: - libwayland-cursor0 - libwayland-egl1 - libwxgtk3.0-gtk3-0v5 + - xclip apps: espanso: