Merge pull request #886 from federico-terzi/dev

v2.1.1-alpha
This commit is contained in:
Federico Terzi 2021-11-27 11:07:28 +01:00 committed by GitHub
commit 283b85818b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 2793 additions and 1022 deletions

2
Cargo.lock generated
View File

@ -572,7 +572,7 @@ dependencies = [
[[package]]
name = "espanso"
version = "2.1.0-alpha"
version = "2.1.1-alpha"
dependencies = [
"anyhow",
"caps",

View File

@ -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<String> {
fn get_text(&self, _: &ClipboardOperationOptions) -> Option<String> {
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() {

View File

@ -37,10 +37,28 @@ mod wayland;
mod cocoa;
pub trait Clipboard {
fn get_text(&self) -> Option<String>;
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<String>;
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<Box<dyn Clipboard>> {
#[cfg(target_os = "linux")]
#[cfg(not(feature = "wayland"))]
pub fn get_clipboard(_: ClipboardOptions) -> Result<Box<dyn Clipboard>> {
info!("using X11NativeClipboard");
Ok(Box::new(x11::native::X11NativeClipboard::new()?))
info!("using X11Clipboard");
Ok(Box::new(x11::X11Clipboard::new()?))
}
#[cfg(target_os = "linux")]

View File

@ -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<String> {
fn get_text(&self, _: &ClipboardOperationOptions) -> Option<String> {
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(),

View File

@ -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<String> {
fn get_text(&self, _: &ClipboardOperationOptions) -> Option<String> {
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())?;

View File

@ -17,4 +17,66 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
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<Self> {
Ok(Self {
native_backend: native::X11NativeClipboard::new()?,
xclip_backend: xclip::XClipClipboard::new(),
})
}
}
impl Clipboard for X11Clipboard {
fn get_text(&self, options: &ClipboardOperationOptions) -> Option<String> {
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)
}
}
}

View File

@ -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<String> {
fn get_text(&self, _: &ClipboardOperationOptions) -> Option<String> {
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() {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> {
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(())
}
}

View File

@ -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(),

View File

@ -46,6 +46,7 @@ pub(crate) struct ParsedConfig {
pub secure_input_notification: Option<bool>,
pub win32_exclude_orphan_events: Option<bool>,
pub win32_keyboard_layout_cache_interval: Option<i64>,
pub x11_use_xclip_backend: Option<bool>,
pub pre_paste_delay: Option<usize>,
pub restore_clipboard_delay: Option<usize>,

View File

@ -112,6 +112,9 @@ pub(crate) struct YAMLConfig {
#[serde(default)]
pub win32_keyboard_layout_cache_interval: Option<i64>,
#[serde(default)]
pub x11_use_xclip_backend: Option<bool>,
// Include/Exclude
#[serde(default)]
pub includes: Option<Vec<String>>,
@ -201,6 +204,7 @@ impl TryFrom<YAMLConfig> 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),

View File

@ -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,

View File

@ -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 {

View File

@ -17,7 +17,9 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
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,
)),
],
}
}

View File

@ -24,3 +24,4 @@ pub mod image_inject;
pub mod key_inject;
pub mod secure_input;
pub mod text_inject;
pub mod text_ui;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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

View File

@ -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,
)
}

View File

@ -101,6 +101,8 @@ pub enum EventType {
IconStatusChange(ui::IconStatusChangeEvent),
DisplaySecureInputTroubleshoot,
ShowSearchBar,
ShowText(ui::ShowTextEvent),
ShowLogs,
// Other
LaunchSecureInputAutoFix,

View File

@ -52,3 +52,9 @@ pub enum IconStatus {
Disabled,
SecureInputDisabled,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShowTextEvent {
pub title: String,
pub text: String,
}

View File

@ -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<bool>,
@ -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!()

View File

@ -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() {

View File

@ -20,6 +20,7 @@
#include "native.h"
#include <string.h>
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#include <vector>
// Events dispatched by espanso are "marked" with a custom location

View File

@ -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;
}

View File

@ -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<KeyPair>,
}
type CharMap = HashMap<String, KeyRecord>;
type SymMap = HashMap<KeySym, KeyRecord>;
@ -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,64 @@ 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 event = XKeyEvent {
let preceding_dead_key = if let Some(dead_key) = dead_key {
let mut dead_key_event = XKeyEvent {
display,
keycode: code_with_offset,
state: modifier_state,
keycode: dead_key.code,
state: dead_key.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 +167,143 @@ impl X11Injector {
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 {
XLookupString(
&event,
Xutf8LookupString(
input_context,
&mut key_event,
buffer.as_mut_ptr(),
(buffer.len() - 1) as i32,
&mut sym,
std::ptr::null(),
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) };
}
}
}
(char_map, sym_map)
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<Vec<Option<KeyPair>>> {
let mut deadkeys = vec![None];
let mut seen_keysyms: HashSet<KeySym> = 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 mut 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,
};
let filter = unsafe { XFilterEvent(&mut event, 0) };
if filter == 1 {
let mut sym: KeySym = 0;
let mut buffer: [c_char; 10] = [0; 10];
unsafe {
Xutf8LookupString(
*input_context,
&mut event,
buffer.as_mut_ptr(),
(buffer.len() - 1) as i32,
&mut sym,
std::ptr::null_mut(),
)
};
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);
}
}
let _reset = unsafe { XmbResetIC(*input_context) };
unsafe { XFree(_reset as *mut c_void) };
}
}
Ok(deadkeys)
}
fn convert_to_record_array(&self, syms: &[KeySym]) -> Result<Vec<KeyRecord>> {
@ -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);
}
}

View File

@ -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");

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -19,6 +19,7 @@
pub mod form;
pub mod search;
pub mod textview;
pub mod troubleshooting;
pub mod welcome;
pub mod wizard;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#define _UNICODE
#include "../common/common.h"
#include "../interop/interop.h"
#include "./textview_gui.h"
#include <wx/clipbrd.h>
#include <vector>
#include <memory>
#include <unordered_map>
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);
}

View File

@ -0,0 +1,223 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="15" />
<object class="Project" expanded="1">
<property name="class_decoration">; </property>
<property name="code_generation">C++</property>
<property name="disconnect_events">1</property>
<property name="disconnect_mode">source_name</property>
<property name="disconnect_php_events">0</property>
<property name="disconnect_python_events">0</property>
<property name="embedded_files_path">res</property>
<property name="encoding">UTF-8</property>
<property name="event_generation">connect</property>
<property name="file">textview_gui</property>
<property name="first_id">1000</property>
<property name="help_provider">none</property>
<property name="indent_with_spaces"></property>
<property name="internationalize">0</property>
<property name="name">TextView</property>
<property name="namespace"></property>
<property name="path">.</property>
<property name="precompiled_header">#define _UNICODE</property>
<property name="relative_path">1</property>
<property name="skip_lua_events">1</property>
<property name="skip_php_events">1</property>
<property name="skip_python_events">1</property>
<property name="ui_table">UI</property>
<property name="use_enum">0</property>
<property name="use_microsoft_bom">0</property>
<object class="Frame" expanded="1">
<property name="aui_managed">0</property>
<property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
<property name="bg">wxSYS_COLOUR_WINDOW</property>
<property name="center">wxBOTH</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="enabled">1</property>
<property name="event_handler">impl_virtual</property>
<property name="extra_style"></property>
<property name="fg"></property>
<property name="font"></property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="maximum_size"></property>
<property name="minimum_size"></property>
<property name="name">TextViewFrame</property>
<property name="pos"></property>
<property name="size">895,545</property>
<property name="style">wxDEFAULT_FRAME_STYLE</property>
<property name="subclass">; ; forward_declare</property>
<property name="title">TextView</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxTAB_TRAVERSAL</property>
<property name="xrc_skip_sizer">1</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer1</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxTextCtrl" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font">,90,90,-1,76,0</property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="maxlength"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">text_content</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxTE_MULTILINE|wxTE_READONLY</property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="value"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">10</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer2</property>
<property name="orient">wxHORIZONTAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="spacer" expanded="1">
<property name="height">0</property>
<property name="permission">protected</property>
<property name="width">0</property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">10</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALL</property>
<property name="proportion">0</property>
<object class="wxButton" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="bitmap"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="current"></property>
<property name="default">1</property>
<property name="default_pane">0</property>
<property name="disabled"></property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="focus"></property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Copy to Clipboard</property>
<property name="margins"></property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">copy_to_clipboard_btn</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="position"></property>
<property name="pressed"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnButtonClick">on_copy_to_clipboard</event>
</object>
</object>
</object>
</object>
</object>
</object>
</object>
</wxFormBuilder_Project>

View File

@ -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 );
}

View File

@ -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 <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/textctrl.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/button.h>
#include <wx/sizer.h>
#include <wx/frame.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// 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();
};

View File

@ -45,8 +45,8 @@
<property name="minimum_size"></property>
<property name="name">WizardFrame</property>
<property name="pos"></property>
<property name="size">550,577</property>
<property name="style">wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU</property>
<property name="size">600,577</property>
<property name="style">wxCAPTION|wxCLOSE_BOX|wxRESIZE_BORDER|wxSYSTEM_MENU</property>
<property name="subclass">; ; forward_declare</property>
<property name="title">Espanso</property>
<property name="tooltip"></property>
@ -178,6 +178,68 @@
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxTAB_TRAVERSAL</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer13</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND | wxALL</property>
<property name="proportion">1</property>
<object class="wxScrolledWindow" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_scrolledWindow2</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="scroll_rate_x">5</property>
<property name="scroll_rate_y">5</property>
<property name="show">1</property>
<property name="size"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxHSCROLL|wxVSCROLL</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer2</property>
@ -444,6 +506,9 @@
<property name="width">0</property>
</object>
</object>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">10</property>
<property name="flag">wxALIGN_RIGHT|wxALL</property>
@ -1593,7 +1658,7 @@
<property name="window_style">wxTAB_TRAVERSAL</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer211</property>
<property name="name">bSizer16</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
@ -1657,6 +1722,68 @@
<property name="wrap">-1</property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND | wxALL</property>
<property name="proportion">1</property>
<object class="wxScrolledWindow" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_scrolledWindow4</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="scroll_rate_x">5</property>
<property name="scroll_rate_y">5</property>
<property name="show">1</property>
<property name="size"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxVSCROLL</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer211</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag"></property>
@ -1670,7 +1797,7 @@
<object class="sizeritem" expanded="1">
<property name="border">10</property>
<property name="flag">wxLEFT|wxRIGHT|wxTOP</property>
<property name="proportion">1</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
@ -1791,20 +1918,13 @@
<property name="window_style"></property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">10</property>
<object class="spacer" expanded="1">
<property name="height">0</property>
<property name="permission">protected</property>
<property name="width">0</property>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<property name="proportion">0</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size">-1,-1</property>
<property name="name">bSizer8</property>
@ -2025,6 +2145,68 @@
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxTAB_TRAVERSAL</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer18</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND | wxALL</property>
<property name="proportion">1</property>
<object class="wxScrolledWindow" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_scrolledWindow6</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="scroll_rate_x">5</property>
<property name="scroll_rate_y">5</property>
<property name="show">1</property>
<property name="size"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxHSCROLL|wxVSCROLL</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer2122</property>
@ -2297,6 +2479,9 @@
<property name="width">0</property>
</object>
</object>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">10</property>
<property name="flag">wxALIGN_RIGHT|wxALL</property>
@ -2427,6 +2612,68 @@
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxTAB_TRAVERSAL</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer20</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND | wxALL</property>
<property name="proportion">1</property>
<object class="wxScrolledWindow" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_scrolledWindow8</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="scroll_rate_x">5</property>
<property name="scroll_rate_y">5</property>
<property name="show">1</property>
<property name="size"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxHSCROLL|wxVSCROLL</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer212</property>
@ -2699,6 +2946,9 @@
<property name="width">0</property>
</object>
</object>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">10</property>
<property name="flag">wxALIGN_RIGHT|wxALL</property>

View File

@ -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 ) );

View File

@ -20,12 +20,12 @@
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/sizer.h>
#include <wx/scrolwin.h>
#include <wx/button.h>
#include <wx/panel.h>
#include <wx/hyperlink.h>
#include <wx/checkbox.h>
#include <wx/scrolwin.h>
#include <wx/simplebook.h>
#include <wx/frame.h>
@ -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();

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
pub use crate::sys::textview::show;
pub struct TextViewOptions {
pub window_icon_path: Option<String>,
pub title: String,
pub content: String,
}

View File

@ -1,6 +1,6 @@
[package]
name = "espanso"
version = "2.1.0-alpha"
version = "2.1.1-alpha"
authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
license = "GPL-3.0"
description = "Cross-platform Text Expander written in Rust"

View File

@ -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);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
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()
}
}

View File

@ -55,6 +55,9 @@ pub fn get_builtin_matches(config: &dyn Config) -> Vec<BuiltInMatch> {
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(),
];

View File

@ -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(),
}
}
}

View File

@ -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<String>,
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

View File

@ -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;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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(())
}
}

View File

@ -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<dyn Context> = 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

View File

@ -55,11 +55,15 @@ impl<'a> MatchSelector for MatchSelectorAdapter<'a> {
let search_items: Vec<SearchItem> = 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,
}

View File

@ -17,21 +17,36 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
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<String> {
self.clipboard.get_text()
self.clipboard.get_text(
&self
.clipboard_operation_options_provider
.get_operation_options(),
)
}
}

View File

@ -17,7 +17,7 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use std::collections::HashMap;
use std::{collections::HashMap, path::Path};
use anyhow::Result;
@ -58,3 +58,8 @@ pub enum FormField {
values: Vec<String>,
},
}
pub trait TextUI {
fn show_text(&self, title: &str, text: &str) -> Result<()>;
fn show_file(&self, title: &str, path: &Path) -> Result<()>;
}

View File

@ -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<String> {
if self.is_support_enabled {
let exec_path = std::env::current_exe().expect("unable to obtain current exec path");

View File

@ -20,3 +20,4 @@
pub mod form;
pub mod manager;
pub mod search;
pub mod textview;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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(())
}
}

View File

@ -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")

View File

@ -42,6 +42,7 @@ fn get_builtin_patches() -> Vec<PatchDefinition> {
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(),

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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()
},
))
},
}
}

View File

@ -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;

View File

@ -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<RMLVOConfig>
);

View File

@ -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: