commit
283b85818b
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -572,7 +572,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "espanso"
|
||||
version = "2.1.0-alpha"
|
||||
version = "2.1.1-alpha"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"caps",
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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())?;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
139
espanso-clipboard/src/x11/xclip/mod.rs
Normal file
139
espanso-clipboard/src/x11/xclip/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
)),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,3 +24,4 @@ pub mod image_inject;
|
|||
pub mod key_inject;
|
||||
pub mod secure_input;
|
||||
pub mod text_inject;
|
||||
pub mod text_ui;
|
||||
|
|
63
espanso-engine/src/dispatch/executor/text_ui.rs
Normal file
63
espanso-engine/src/dispatch/executor/text_ui.rs
Normal 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
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -101,6 +101,8 @@ pub enum EventType {
|
|||
IconStatusChange(ui::IconStatusChangeEvent),
|
||||
DisplaySecureInputTroubleshoot,
|
||||
ShowSearchBar,
|
||||
ShowText(ui::ShowTextEvent),
|
||||
ShowLogs,
|
||||
|
||||
// Other
|
||||
LaunchSecureInputAutoFix,
|
||||
|
|
|
@ -52,3 +52,9 @@ pub enum IconStatus {
|
|||
Disabled,
|
||||
SecureInputDisabled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ShowTextEvent {
|
||||
pub title: String,
|
||||
pub text: String,
|
||||
}
|
||||
|
|
|
@ -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!()
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,169 @@ impl X11Injector {
|
|||
})
|
||||
}
|
||||
|
||||
fn generate_maps(display: *mut Display) -> (CharMap, SymMap) {
|
||||
fn generate_maps(display: *mut Display) -> Result<(CharMap, SymMap)> {
|
||||
debug!("generating key maps");
|
||||
|
||||
let mut char_map = HashMap::new();
|
||||
let mut sym_map = HashMap::new();
|
||||
|
||||
let root_window = unsafe { XDefaultRootWindow(display) };
|
||||
let input_method = unsafe {
|
||||
XOpenIM(
|
||||
display,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if input_method.is_null() {
|
||||
bail!("could not open input method");
|
||||
}
|
||||
let _im_guard = scopeguard::guard((), |_| {
|
||||
unsafe { XCloseIM(input_method) };
|
||||
});
|
||||
|
||||
let input_context = unsafe {
|
||||
XCreateIC(
|
||||
input_method,
|
||||
XNInputStyle_0.as_ptr(),
|
||||
XIMPreeditNothing | XIMStatusNothing,
|
||||
XNClientWindow_0.as_ptr(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if input_context.is_null() {
|
||||
bail!("could not open input context");
|
||||
}
|
||||
let _ic_guard = scopeguard::guard((), |_| {
|
||||
unsafe { XDestroyIC(input_context) };
|
||||
});
|
||||
|
||||
let deadkeys = Self::find_deadkeys(display, &input_context)?;
|
||||
|
||||
// Cycle through all state/code combinations to populate the reverse lookup tables
|
||||
for key_code in 0..256u32 {
|
||||
for modifier_state in 0..256u32 {
|
||||
for dead_key in deadkeys.iter() {
|
||||
let code_with_offset = key_code + EVDEV_OFFSET;
|
||||
|
||||
let preceding_dead_key = if let Some(dead_key) = dead_key {
|
||||
let mut dead_key_event = XKeyEvent {
|
||||
display,
|
||||
keycode: dead_key.code,
|
||||
state: dead_key.state,
|
||||
|
||||
// These might not even need to be filled
|
||||
window: 0,
|
||||
root: 0,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: KeyPress,
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
unsafe { XFilterEvent(&mut dead_key_event, 0) };
|
||||
|
||||
Some(*dead_key)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut key_event = XKeyEvent {
|
||||
display,
|
||||
keycode: code_with_offset,
|
||||
state: modifier_state,
|
||||
|
||||
// These might not even need to be filled
|
||||
window: 0,
|
||||
root: 0,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: KeyPress,
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
unsafe { XFilterEvent(&mut key_event, 0) };
|
||||
let mut sym: KeySym = 0;
|
||||
let mut buffer: [c_char; 10] = [0; 10];
|
||||
|
||||
let result = unsafe {
|
||||
Xutf8LookupString(
|
||||
input_context,
|
||||
&mut key_event,
|
||||
buffer.as_mut_ptr(),
|
||||
(buffer.len() - 1) as i32,
|
||||
&mut sym,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
let key_record = KeyRecord {
|
||||
main: KeyPair {
|
||||
code: code_with_offset,
|
||||
state: modifier_state,
|
||||
},
|
||||
preceding_dead_key,
|
||||
};
|
||||
|
||||
// Keysym was found
|
||||
if sym != 0 {
|
||||
sym_map.entry(sym).or_insert(key_record);
|
||||
};
|
||||
|
||||
// Char was found
|
||||
if result > 0 {
|
||||
let raw_string = unsafe { CStr::from_ptr(buffer.as_ptr()) };
|
||||
let string = raw_string.to_string_lossy().to_string();
|
||||
char_map.entry(string).or_insert(key_record);
|
||||
};
|
||||
|
||||
// We need to reset the context state to prevent
|
||||
// deadkeys effect to propagate to the next combination
|
||||
let _reset = unsafe { XmbResetIC(input_context) };
|
||||
unsafe { XFree(_reset as *mut c_void) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Populated char_map with {} symbols", char_map.len());
|
||||
debug!("Populated sym_map with {} symbols", sym_map.len());
|
||||
debug!("Detected {} dead key combinations", deadkeys.len());
|
||||
|
||||
Ok((char_map, sym_map))
|
||||
}
|
||||
|
||||
fn find_deadkeys(display: *mut Display, input_context: &XIC) -> Result<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 event = XKeyEvent {
|
||||
let mut event = XKeyEvent {
|
||||
display,
|
||||
keycode: code_with_offset,
|
||||
state: modifier_state,
|
||||
|
||||
// These might not even need to be filled
|
||||
window: root_window,
|
||||
root: root_window,
|
||||
window: 0,
|
||||
root: 0,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: KeyRelease,
|
||||
type_: KeyPress,
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
|
@ -115,38 +272,38 @@ impl X11Injector {
|
|||
send_event: 0,
|
||||
};
|
||||
|
||||
let mut sym: KeySym = 0;
|
||||
let mut buffer: [c_char; 10] = [0; 10];
|
||||
let result = unsafe {
|
||||
XLookupString(
|
||||
&event,
|
||||
buffer.as_mut_ptr(),
|
||||
(buffer.len() - 1) as i32,
|
||||
&mut sym,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
let filter = unsafe { XFilterEvent(&mut event, 0) };
|
||||
if filter == 1 {
|
||||
let mut sym: KeySym = 0;
|
||||
let mut buffer: [c_char; 10] = [0; 10];
|
||||
|
||||
let key_record = KeyRecord {
|
||||
code: code_with_offset,
|
||||
state: modifier_state,
|
||||
};
|
||||
unsafe {
|
||||
Xutf8LookupString(
|
||||
*input_context,
|
||||
&mut event,
|
||||
buffer.as_mut_ptr(),
|
||||
(buffer.len() - 1) as i32,
|
||||
&mut sym,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
// Keysym was found
|
||||
if sym != 0 {
|
||||
sym_map.entry(sym).or_insert(key_record);
|
||||
if sym != 0 && !seen_keysyms.contains(&sym) {
|
||||
let key_record = KeyPair {
|
||||
code: code_with_offset,
|
||||
state: modifier_state,
|
||||
};
|
||||
deadkeys.push(Some(key_record));
|
||||
seen_keysyms.insert(sym);
|
||||
}
|
||||
}
|
||||
|
||||
// Char was found
|
||||
if result > 0 {
|
||||
let raw_string = unsafe { CStr::from_ptr(buffer.as_ptr()) };
|
||||
let string = raw_string.to_string_lossy().to_string();
|
||||
char_map.entry(string).or_insert(key_record);
|
||||
}
|
||||
let _reset = unsafe { XmbResetIC(*input_context) };
|
||||
unsafe { XFree(_reset as *mut c_void) };
|
||||
}
|
||||
}
|
||||
|
||||
(char_map, sym_map)
|
||||
Ok(deadkeys)
|
||||
}
|
||||
|
||||
fn convert_to_record_array(&self, syms: &[KeySym]) -> Result<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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
pub mod form;
|
||||
pub mod search;
|
||||
pub mod textview;
|
||||
pub mod troubleshooting;
|
||||
pub mod welcome;
|
||||
pub mod wizard;
|
||||
|
|
40
espanso-modulo/src/sys/textview/mod.rs
Normal file
40
espanso-modulo/src/sys/textview/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
104
espanso-modulo/src/sys/textview/textview.cpp
Normal file
104
espanso-modulo/src/sys/textview/textview.cpp
Normal 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);
|
||||
}
|
223
espanso-modulo/src/sys/textview/textview.fbp
Normal file
223
espanso-modulo/src/sys/textview/textview.fbp
Normal 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>
|
56
espanso-modulo/src/sys/textview/textview_gui.cpp
Normal file
56
espanso-modulo/src/sys/textview/textview_gui.cpp
Normal 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 );
|
||||
|
||||
}
|
50
espanso-modulo/src/sys/textview/textview_gui.h
Normal file
50
espanso-modulo/src/sys/textview/textview_gui.h
Normal 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();
|
||||
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -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 ) );
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
26
espanso-modulo/src/textview/mod.rs
Normal file
26
espanso-modulo/src/textview/mod.rs
Normal 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,
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
51
espanso/src/cli/modulo/textview.rs
Normal file
51
espanso/src/cli/modulo/textview.rs
Normal 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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
48
espanso/src/cli/worker/engine/dispatch/executor/text_ui.rs
Normal file
48
espanso/src/cli/worker/engine/dispatch/executor/text_ui.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<()>;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -20,3 +20,4 @@
|
|||
pub mod form;
|
||||
pub mod manager;
|
||||
pub mod search;
|
||||
pub mod textview;
|
||||
|
|
51
espanso/src/gui/modulo/textview.rs
Normal file
51
espanso/src/gui/modulo/textview.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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(),
|
||||
|
|
41
espanso/src/patch/patches/linux/gedit_x11.rs
Normal file
41
espanso/src/patch/patches/linux/gedit_x11.rs
Normal 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()
|
||||
},
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue
Block a user