Add license header and formatting

This commit is contained in:
Federico Terzi 2021-03-09 16:06:50 +01:00
parent 4143caff3d
commit e8881d0faf
42 changed files with 711 additions and 263 deletions

View File

@ -1,12 +1,31 @@
/*
* 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::Result;
use std::{collections::HashSet, path::Path};
use thiserror::Error;
use anyhow::Result;
mod path;
mod parse;
mod util;
mod path;
mod resolve;
mod store;
mod util;
pub trait Config {
fn label(&self) -> &str;
@ -42,4 +61,4 @@ pub enum ConfigStoreError {
#[error("io error")]
IOError(#[from] std::io::Error),
}
}

View File

@ -1,6 +1,25 @@
/*
* 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::Result;
use thiserror::Error;
use std::{convert::TryInto, path::Path};
use thiserror::Error;
mod yaml;
@ -26,12 +45,8 @@ impl ParsedConfig {
pub fn load(path: &Path) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
match yaml::YAMLConfig::parse_from_str(&content) {
Ok(config) => {
Ok(config.try_into()?)
}
Err(err) => {
Err(ParsedConfigError::LoadFailed(err).into())
}
Ok(config) => Ok(config.try_into()?),
Err(err) => Err(ParsedConfigError::LoadFailed(err).into()),
}
}
}
@ -40,4 +55,4 @@ impl ParsedConfig {
pub enum ParsedConfigError {
#[error("can't load config `{0}`")]
LoadFailed(#[from] anyhow::Error),
}
}

View File

@ -1,3 +1,22 @@
/*
* 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::Result;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;

View File

@ -1,7 +1,23 @@
use std::{
collections::HashSet,
path::{Path},
};
/*
* 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::{collections::HashSet, path::Path};
use glob::glob;
use log::error;
@ -36,7 +52,10 @@ pub fn calculate_paths<'a>(
path_set.insert(canonical_path.to_string_lossy().to_string());
}
Err(err) => {
error!("unable to canonicalize path from glob: {:?}, with error: {}", path, err);
error!(
"unable to canonicalize path from glob: {:?}, with error: {}",
path, err
);
}
}
}

View File

@ -1,3 +1,22 @@
/*
* 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 super::{parse::ParsedConfig, path::calculate_paths, util::os_matches, AppProperties, Config};
use crate::merge;
use anyhow::Result;
@ -14,7 +33,6 @@ pub(crate) struct ResolvedConfig {
parsed: ParsedConfig,
// Generated properties
match_paths: Vec<String>,
filter_title: Option<Regex>,
@ -58,38 +76,35 @@ impl Config for ResolvedConfig {
true
};
let is_title_match =
if let Some(title_regex) = self.filter_title.as_ref() {
if let Some(title) = app.title {
title_regex.is_match(title)
} else {
false
}
let is_title_match = if let Some(title_regex) = self.filter_title.as_ref() {
if let Some(title) = app.title {
title_regex.is_match(title)
} else {
true
};
false
}
} else {
true
};
let is_exec_match =
if let Some(exec_regex) = self.filter_exec.as_ref() {
if let Some(exec) = app.exec {
exec_regex.is_match(exec)
} else {
false
}
let is_exec_match = if let Some(exec_regex) = self.filter_exec.as_ref() {
if let Some(exec) = app.exec {
exec_regex.is_match(exec)
} else {
true
};
false
}
} else {
true
};
let is_class_match =
if let Some(class_regex) = self.filter_class.as_ref() {
if let Some(class) = app.class {
class_regex.is_match(class)
} else {
false
}
let is_class_match = if let Some(class_regex) = self.filter_class.as_ref() {
if let Some(class) = app.class {
class_regex.is_match(class)
} else {
true
};
false
}
} else {
true
};
// All the filters that have been specified must be true to define a match
is_os_match && is_exec_match && is_title_match && is_class_match
@ -110,7 +125,9 @@ impl ResolvedConfig {
.parent()
.ok_or_else(ResolveError::ParentResolveFailed)?;
let match_paths = Self::generate_match_paths(&config, base_dir).into_iter().collect();
let match_paths = Self::generate_match_paths(&config, base_dir)
.into_iter()
.collect();
let filter_title = if let Some(filter_title) = config.filter_title.as_deref() {
Some(Regex::new(filter_title)?)
@ -431,7 +448,7 @@ mod tests {
sub_file.to_string_lossy().to_string(),
];
expected.sort();
let mut result = config.match_paths().to_vec();
result.sort();
@ -491,9 +508,7 @@ mod tests {
result.sort();
assert_eq!(result, expected.as_slice());
let expected = vec![
base_file.to_string_lossy().to_string()
];
let expected = vec![base_file.to_string_lossy().to_string()];
assert_eq!(parent.match_paths(), expected.as_slice());
});

View File

@ -1,4 +1,23 @@
use super::{Config, ConfigStore, ConfigStoreError, resolve::ResolvedConfig};
/*
* 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 super::{resolve::ResolvedConfig, Config, ConfigStore, ConfigStoreError};
use anyhow::Result;
use log::{debug, error};
use std::{collections::HashSet, path::Path};
@ -56,7 +75,11 @@ impl DefaultConfigStore {
for entry in std::fs::read_dir(config_dir).map_err(ConfigStoreError::IOError)? {
let entry = entry?;
let config_file = entry.path();
let extension = config_file.extension().unwrap_or_default().to_string_lossy().to_lowercase();
let extension = config_file
.extension()
.unwrap_or_default()
.to_string_lossy()
.to_lowercase();
// Additional config files are loaded best-effort
if config_file.is_file()
@ -69,7 +92,10 @@ impl DefaultConfigStore {
debug!("loaded config at path: {:?}", config_file);
}
Err(err) => {
error!("unable to load config at path: {:?}, with error: {}", config_file, err);
error!(
"unable to load config at path: {:?}, with error: {}",
config_file, err
);
}
}
}

View File

@ -1,3 +1,22 @@
/*
* 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/>.
*/
#[macro_export]
macro_rules! merge {
( $t:ident, $child:expr, $parent:expr, $( $x:ident ),* ) => {
@ -7,7 +26,7 @@ macro_rules! merge {
$child.$x = $parent.$x.clone();
}
)*
// Build a temporary object to verify that all fields
// are being used at compile time
$t {
@ -41,7 +60,6 @@ mod tests {
assert!(!os_matches("invalid"));
}
#[test]
#[cfg(target_os = "macos")]
fn os_matches_macos() {
@ -59,4 +77,4 @@ mod tests {
assert!(!os_matches("linux"));
assert!(!os_matches("invalid"));
}
}
}

View File

@ -1,3 +1,22 @@
/*
* 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::atomic::{AtomicUsize, Ordering};
static STRUCT_COUNTER: AtomicUsize = AtomicUsize::new(0);
@ -10,4 +29,4 @@ pub type StructId = usize;
/// that is incremented for each struct.
pub fn next_id() -> StructId {
STRUCT_COUNTER.fetch_add(1, Ordering::SeqCst)
}
}

View File

@ -17,24 +17,24 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use std::path::Path;
use anyhow::Result;
use config::ConfigStore;
use matches::store::MatchStore;
use anyhow::Result;
use std::path::Path;
use thiserror::Error;
#[macro_use]
extern crate lazy_static;
mod util;
mod counter;
pub mod config;
mod counter;
pub mod matches;
mod util;
pub fn load(base_path: &Path) -> Result<(impl ConfigStore, impl MatchStore)> {
let config_dir = base_path.join("config");
if !config_dir.exists() || !config_dir.is_dir() {
return Err(ConfigError::MissingConfigDir().into())
return Err(ConfigError::MissingConfigDir().into());
}
let config_store = config::load_store(&config_dir)?;
@ -55,62 +55,100 @@ pub enum ConfigError {
#[cfg(test)]
mod tests {
use super::*;
use config::{AppProperties, ConfigStore};
use crate::util::tests::use_test_directory;
use config::{AppProperties, ConfigStore};
#[test]
fn load_works_correctly() {
use_test_directory(|base, match_dir, config_dir| {
let base_file = match_dir.join("base.yml");
std::fs::write(&base_file, r#"
std::fs::write(
&base_file,
r#"
matches:
- trigger: "hello"
replace: "world"
"#).unwrap();
"#,
)
.unwrap();
let another_file = match_dir.join("another.yml");
std::fs::write(&another_file, r#"
std::fs::write(
&another_file,
r#"
imports:
- "_sub.yml"
matches:
- trigger: "hello2"
replace: "world2"
"#).unwrap();
"#,
)
.unwrap();
let under_file = match_dir.join("_sub.yml");
std::fs::write(&under_file, r#"
std::fs::write(
&under_file,
r#"
matches:
- trigger: "hello3"
replace: "world3"
"#).unwrap();
"#,
)
.unwrap();
let config_file = config_dir.join("default.yml");
std::fs::write(&config_file, "").unwrap();
let custom_config_file = config_dir.join("custom.yml");
std::fs::write(&custom_config_file, r#"
std::fs::write(
&custom_config_file,
r#"
filter_title: "Chrome"
use_standard_includes: false
includes: ["../match/another.yml"]
"#).unwrap();
"#,
)
.unwrap();
let (config_store, match_store) = load(&base).unwrap();
assert_eq!(config_store.default().match_paths().len(), 2);
assert_eq!(config_store.active(&AppProperties {
title: Some("Google Chrome"),
class: None,
exec: None,
}).match_paths().len(), 1);
assert_eq!(
config_store
.active(&AppProperties {
title: Some("Google Chrome"),
class: None,
exec: None,
})
.match_paths()
.len(),
1
);
assert_eq!(match_store.query(config_store.default().match_paths()).matches.len(), 3);
assert_eq!(match_store.query(config_store.active(&AppProperties {
title: Some("Chrome"),
class: None,
exec: None,
}).match_paths()).matches.len(), 2);
assert_eq!(
match_store
.query(config_store.default().match_paths())
.matches
.len(),
3
);
assert_eq!(
match_store
.query(
config_store
.active(&AppProperties {
title: Some("Chrome"),
class: None,
exec: None,
})
.match_paths()
)
.matches
.len(),
2
);
});
}

View File

@ -1,3 +1,22 @@
/*
* 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::Result;
use std::path::Path;
use thiserror::Error;
@ -14,9 +33,7 @@ trait Importer {
}
lazy_static! {
static ref IMPORTERS: Vec<Box<dyn Importer + Sync + Send>> = vec![
Box::new(YAMLImporter::new()),
];
static ref IMPORTERS: Vec<Box<dyn Importer + Sync + Send>> = vec![Box::new(YAMLImporter::new()),];
}
pub(crate) fn load_match_group(path: &Path) -> Result<MatchGroup> {
@ -26,7 +43,7 @@ pub(crate) fn load_match_group(path: &Path) -> Result<MatchGroup> {
let importer = IMPORTERS
.iter()
.find(|importer| importer.is_supported(&extension));
match importer {
Some(importer) => match importer.load_group(path) {
Ok(group) => Ok(group),
@ -62,7 +79,13 @@ mod tests {
let file = match_dir.join("base.invalid");
std::fs::write(&file, "test").unwrap();
assert!(matches!(load_match_group(&file).unwrap_err().downcast::<LoadError>().unwrap(), LoadError::InvalidFormat()));
assert!(matches!(
load_match_group(&file)
.unwrap_err()
.downcast::<LoadError>()
.unwrap(),
LoadError::InvalidFormat()
));
});
}
@ -72,7 +95,13 @@ mod tests {
let file = match_dir.join("base");
std::fs::write(&file, "test").unwrap();
assert!(matches!(load_match_group(&file).unwrap_err().downcast::<LoadError>().unwrap(), LoadError::MissingExtension()));
assert!(matches!(
load_match_group(&file)
.unwrap_err()
.downcast::<LoadError>()
.unwrap(),
LoadError::MissingExtension()
));
});
}
@ -82,7 +111,13 @@ mod tests {
let file = match_dir.join("base.yml");
std::fs::write(&file, "test").unwrap();
assert!(matches!(load_match_group(&file).unwrap_err().downcast::<LoadError>().unwrap(), LoadError::ParsingError(_)));
assert!(matches!(
load_match_group(&file)
.unwrap_err()
.downcast::<LoadError>()
.unwrap(),
LoadError::ParsingError(_)
));
});
}
@ -90,11 +125,15 @@ mod tests {
fn load_group_yaml_format() {
use_test_directory(|_, match_dir, _| {
let file = match_dir.join("base.yml");
std::fs::write(&file, r#"
std::fs::write(
&file,
r#"
matches:
- trigger: "hello"
replace: "world"
"#).unwrap();
"#,
)
.unwrap();
assert_eq!(load_match_group(&file).unwrap().matches.len(), 1);
});
@ -104,11 +143,15 @@ mod tests {
fn load_group_yaml_format_2() {
use_test_directory(|_, match_dir, _| {
let file = match_dir.join("base.yaml");
std::fs::write(&file, r#"
std::fs::write(
&file,
r#"
matches:
- trigger: "hello"
replace: "world"
"#).unwrap();
"#,
)
.unwrap();
assert_eq!(load_match_group(&file).unwrap().matches.len(), 1);
});
@ -118,11 +161,15 @@ mod tests {
fn load_group_yaml_format_casing() {
use_test_directory(|_, match_dir, _| {
let file = match_dir.join("base.YML");
std::fs::write(&file, r#"
std::fs::write(
&file,
r#"
matches:
- trigger: "hello"
replace: "world"
"#).unwrap();
"#,
)
.unwrap();
assert_eq!(load_match_group(&file).unwrap().matches.len(), 1);
});

View File

@ -1,3 +1,22 @@
/*
* 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::matches::{
group::{path::resolve_imports, MatchGroup},
Match, Variable,
@ -140,9 +159,9 @@ impl TryFrom<YAMLVariable> for Variable {
#[cfg(test)]
mod tests {
use super::*;
use std::fs::create_dir_all;
use serde_yaml::{Mapping, Value};
use crate::{matches::Match, util::tests::use_test_directory};
use serde_yaml::{Mapping, Value};
use std::fs::create_dir_all;
fn create_match(yaml: &str) -> Result<Match> {
let yaml_match: YAMLMatch = serde_yaml::from_str(yaml)?;
@ -388,7 +407,9 @@ mod tests {
create_dir_all(&sub_dir).unwrap();
let base_file = match_dir.join("base.yml");
std::fs::write(&base_file, r#"
std::fs::write(
&base_file,
r#"
imports:
- "sub/sub.yml"
- "invalid/import.yml" # This should be discarded
@ -400,14 +421,16 @@ mod tests {
matches:
- trigger: "hello"
replace: "world"
"#).unwrap();
"#,
)
.unwrap();
let sub_file = sub_dir.join("sub.yml");
std::fs::write(&sub_file, "").unwrap();
std::fs::write(&sub_file, "").unwrap();
let importer = YAMLImporter::new();
let group = importer.load_group(&base_file).unwrap();
let vars = vec![Variable {
name: "var1".to_string(),
var_type: "test".to_string(),
@ -418,23 +441,19 @@ mod tests {
assert_eq!(
group,
MatchGroup {
imports: vec![
sub_file.to_string_lossy().to_string(),
],
imports: vec![sub_file.to_string_lossy().to_string(),],
global_vars: vars,
matches: vec![
Match {
cause: MatchCause::Trigger(TriggerCause {
triggers: vec!["hello".to_string()],
..Default::default()
}),
effect: MatchEffect::Text(TextEffect {
replace: "world".to_string(),
..Default::default()
}),
matches: vec![Match {
cause: MatchCause::Trigger(TriggerCause {
triggers: vec!["hello".to_string()],
..Default::default()
}
],
}),
effect: MatchEffect::Text(TextEffect {
replace: "world".to_string(),
..Default::default()
}),
..Default::default()
}],
}
)
});

View File

@ -1,3 +1,22 @@
/*
* 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::{collections::HashMap, path::Path};
use anyhow::Result;
@ -98,4 +117,4 @@ pub struct YAMLVariable {
fn default_params() -> Mapping {
Mapping::new()
}
}

View File

@ -1,7 +1,24 @@
/*
* 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::Result;
use std::{
path::{Path},
};
use std::path::Path;
use super::{Match, Variable};

View File

@ -1,3 +1,22 @@
/*
* 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::Result;
use log::error;
use std::path::{Path, PathBuf};

View File

@ -1,3 +1,22 @@
/*
* 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 serde_yaml::Mapping;
use crate::counter::{next_id, StructId};

View File

@ -1,3 +1,22 @@
/*
* 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 log::error;
use std::{
collections::{HashMap, HashSet},
@ -629,7 +648,7 @@ mod tests {
.cloned()
.collect::<Vec<Match>>(),
create_matches(&[
("hello", "world3"), // This appears only once, though it appears 2 times
("hello", "world3"), // This appears only once, though it appears 2 times
("hello", "world2"),
("foo", "bar"),
("hello", "world"),

View File

@ -1,3 +1,22 @@
/*
* 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 super::{Match, Variable};
mod default;
@ -17,4 +36,4 @@ pub fn new() -> impl MatchStore {
// TODO: here we can replace the DefaultMatchStore with a caching wrapper
// that returns the same response for the given "paths" query
default::DefaultMatchStore::new()
}
}

View File

@ -1,3 +1,22 @@
/*
* 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/>.
*/
/// Check if the given string represents an empty YAML.
/// In other words, it checks if the document is only composed
/// of spaces and/or comments
@ -5,7 +24,7 @@ pub fn is_yaml_empty(yaml: &str) -> bool {
for line in yaml.lines() {
let trimmed_line = line.trim();
if !trimmed_line.starts_with("#") && !trimmed_line.is_empty() {
return false
return false;
}
}
@ -48,4 +67,4 @@ pub mod tests {
fn is_yaml_empty_document_with_content() {
assert_eq!(is_yaml_empty("\nfield: true\n"), false);
}
}
}

View File

@ -10,7 +10,13 @@ use thiserror::Error;
use crate::KeyboardConfig;
use super::{context::Context, ffi::{XKB_KEYMAP_COMPILE_NO_FLAGS, xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref, xkb_rule_names}};
use super::{
context::Context,
ffi::{
xkb_keymap, xkb_keymap_new_from_names, xkb_keymap_unref, xkb_rule_names,
XKB_KEYMAP_COMPILE_NO_FLAGS,
},
};
pub struct Keymap {
keymap: *mut xkb_keymap,
@ -18,17 +24,11 @@ pub struct Keymap {
impl Keymap {
pub fn new(context: &Context, rmlvo: Option<KeyboardConfig>) -> Result<Keymap> {
let names = rmlvo.map(|rmlvo| {
Self::generate_names(rmlvo)
});
let names = rmlvo.map(|rmlvo| Self::generate_names(rmlvo));
let names_ptr = names.map_or(std::ptr::null(), |names| &names);
let raw_keymap = unsafe {
xkb_keymap_new_from_names(
context.get_handle(),
names_ptr,
XKB_KEYMAP_COMPILE_NO_FLAGS,
)
xkb_keymap_new_from_names(context.get_handle(), names_ptr, XKB_KEYMAP_COMPILE_NO_FLAGS)
};
let keymap = scopeguard::guard(raw_keymap, |raw_keymap| unsafe {
xkb_keymap_unref(raw_keymap);
@ -48,11 +48,21 @@ impl Keymap {
}
fn generate_names(rmlvo: KeyboardConfig) -> xkb_rule_names {
let rules = rmlvo.rules.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let model = rmlvo.model.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let layout = rmlvo.layout.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let variant = rmlvo.variant.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let options = rmlvo.options.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let rules = rmlvo
.rules
.map(|s| CString::new(s).expect("unable to create CString for keymap"));
let model = rmlvo
.model
.map(|s| CString::new(s).expect("unable to create CString for keymap"));
let layout = rmlvo
.layout
.map(|s| CString::new(s).expect("unable to create CString for keymap"));
let variant = rmlvo
.variant
.map(|s| CString::new(s).expect("unable to create CString for keymap"));
let options = rmlvo
.options
.map(|s| CString::new(s).expect("unable to create CString for keymap"));
xkb_rule_names {
rules: rules.map_or(std::ptr::null(), |s| s.as_ptr()),

View File

@ -36,10 +36,10 @@ use libc::{
use log::{error, trace};
use thiserror::Error;
use crate::{KeyboardConfig, Source, SourceCallback, SourceCreationOptions, event::Status::*};
use crate::event::Variant::*;
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
use crate::event::{Key::*, MouseButton, MouseEvent};
use crate::{event::Status::*, KeyboardConfig, Source, SourceCallback, SourceCreationOptions};
use self::device::{DeviceError, RawInputEvent};
@ -72,7 +72,8 @@ impl EVDEVSource {
impl Source for EVDEVSource {
fn initialize(&mut self) -> Result<()> {
let context = Context::new().expect("unable to obtain xkb context");
let keymap = Keymap::new(&context, self._keyboard_rmlvo.clone()).expect("unable to create xkb keymap");
let keymap =
Keymap::new(&context, self._keyboard_rmlvo.clone()).expect("unable to create xkb keymap");
match get_devices(&keymap) {
Ok(devices) => self.devices = devices,
@ -140,9 +141,7 @@ impl Source for EVDEVSource {
if unsafe { *errno_ptr } == EINTR {
continue;
} else {
error!("Could not poll for events, {}", unsafe {
*errno_ptr
});
error!("Could not poll for events, {}", unsafe { *errno_ptr });
return Err(EVDEVSourceError::Internal().into());
}
}

View File

@ -50,7 +50,7 @@ pub trait Source {
pub struct SourceCreationOptions {
// Only relevant in X11 Linux systems, use the EVDEV backend instead of X11.
use_evdev: bool,
// Can be used to overwrite the keymap configuration
// used by espanso to inject key presses.
evdev_keyboard_rmlvo: Option<KeyboardConfig>,
@ -107,4 +107,3 @@ pub fn get_source(options: SourceCreationOptions) -> Result<Box<dyn Source>> {
info!("using EVDEVSource");
Ok(Box::new(evdev::EVDEVSource::new(options)))
}

View File

@ -31,10 +31,10 @@ use log::{error, trace, warn};
use anyhow::Result;
use thiserror::Error;
use crate::{Source, SourceCallback, event::Status::*};
use crate::event::Variant::*;
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
use crate::event::{Key::*, MouseButton, MouseEvent};
use crate::{event::Status::*, Source, SourceCallback};
const INPUT_EVENT_TYPE_KEYBOARD: i32 = 1;
const INPUT_EVENT_TYPE_MOUSE: i32 = 2;
@ -97,8 +97,6 @@ impl CocoaSource {
receiver: LazyCell::new(),
}
}
}
impl Source for CocoaSource {
@ -139,7 +137,7 @@ impl Source for CocoaSource {
}
} else {
error!("Unable to start event loop if CocoaSource receiver is null");
return Err(CocoaSourceError::Unknown().into())
return Err(CocoaSourceError::Unknown().into());
}
Ok(())

View File

@ -26,10 +26,10 @@ use widestring::U16CStr;
use anyhow::Result;
use thiserror::Error;
use crate::{Source, SourceCallback, event::Status::*};
use crate::event::Variant::*;
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
use crate::event::{Key::*, MouseButton, MouseEvent};
use crate::{event::Status::*, Source, SourceCallback};
const INPUT_LEFT_VARIANT: i32 = 1;
const INPUT_RIGHT_VARIANT: i32 = 2;
@ -116,7 +116,7 @@ impl Source for Win32Source {
if self.callback.fill(event_callback).is_err() {
error!("Unable to set Win32Source event callback");
return Err(Win32SourceError::Unknown().into())
return Err(Win32SourceError::Unknown().into());
}
extern "C" fn callback(_self: *mut Win32Source, event: RawInputEvent) {
@ -134,7 +134,7 @@ impl Source for Win32Source {
if error_code <= 0 {
error!("Win32Source eventloop returned a negative error code");
return Err(Win32SourceError::Unknown().into())
return Err(Win32SourceError::Unknown().into());
}
Ok(())

View File

@ -25,10 +25,10 @@ use log::{error, trace, warn};
use anyhow::Result;
use thiserror::Error;
use crate::{Source, SourceCallback, event::Status::*};
use crate::event::Variant::*;
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
use crate::event::{Key::*, MouseButton, MouseEvent};
use crate::{event::Status::*, Source, SourceCallback};
const INPUT_EVENT_TYPE_KEYBOARD: i32 = 1;
const INPUT_EVENT_TYPE_MOUSE: i32 = 2;
@ -87,8 +87,6 @@ impl X11Source {
pub fn is_compatible() -> bool {
unsafe { detect_check_x11() != 0 }
}
}
impl Source for X11Source {

View File

@ -81,4 +81,4 @@ extern "C" {
pub fn setup_uinput_device(fd: c_int) -> c_int;
pub fn uinput_emit(fd: c_int, code: c_uint, pressed: c_int);
}
}

View File

@ -24,18 +24,12 @@ pub struct Keymap {
impl Keymap {
pub fn new(context: &Context, rmlvo: Option<KeyboardConfig>) -> Result<Keymap> {
let names = rmlvo.map(|rmlvo| {
Self::generate_names(rmlvo)
});
let names = rmlvo.map(|rmlvo| Self::generate_names(rmlvo));
let names_ptr = names.map_or(std::ptr::null(), |names| &names);
let raw_keymap = unsafe {
xkb_keymap_new_from_names(
context.get_handle(),
names_ptr,
XKB_KEYMAP_COMPILE_NO_FLAGS,
)
xkb_keymap_new_from_names(context.get_handle(), names_ptr, XKB_KEYMAP_COMPILE_NO_FLAGS)
};
let keymap = scopeguard::guard(raw_keymap, |raw_keymap| unsafe {
xkb_keymap_unref(raw_keymap);
@ -55,11 +49,21 @@ impl Keymap {
}
fn generate_names(rmlvo: KeyboardConfig) -> xkb_rule_names {
let rules = rmlvo.rules.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let model = rmlvo.model.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let layout = rmlvo.layout.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let variant = rmlvo.variant.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let options = rmlvo.options.map(|s| { CString::new(s).expect("unable to create CString for keymap") });
let rules = rmlvo
.rules
.map(|s| CString::new(s).expect("unable to create CString for keymap"));
let model = rmlvo
.model
.map(|s| CString::new(s).expect("unable to create CString for keymap"));
let layout = rmlvo
.layout
.map(|s| CString::new(s).expect("unable to create CString for keymap"));
let variant = rmlvo
.variant
.map(|s| CString::new(s).expect("unable to create CString for keymap"));
let options = rmlvo
.options
.map(|s| CString::new(s).expect("unable to create CString for keymap"));
xkb_rule_names {
rules: rules.map_or(std::ptr::null(), |s| s.as_ptr()),

View File

@ -25,7 +25,7 @@ mod uinput;
use std::{
collections::{HashMap, HashSet},
ffi::{CString},
ffi::CString,
};
use context::Context;
@ -34,10 +34,7 @@ use log::error;
use std::iter::FromIterator;
use uinput::UInputDevice;
use crate::{
linux::raw_keys::{convert_to_sym_array},
InjectorCreationOptions,
};
use crate::{linux::raw_keys::convert_to_sym_array, InjectorCreationOptions};
use anyhow::Result;
use itertools::Itertools;
use thiserror::Error;
@ -120,7 +117,8 @@ impl EVDEVInjector {
}
let context = Context::new().expect("unable to obtain xkb context");
let keymap = Keymap::new(&context, options.evdev_keyboard_rmlvo).expect("unable to create xkb keymap");
let keymap =
Keymap::new(&context, options.evdev_keyboard_rmlvo).expect("unable to create xkb keymap");
let (char_map, sym_map) =
Self::generate_maps(&modifiers, max_modifier_combination_len, &keymap)?;
@ -294,7 +292,7 @@ impl Injector for EVDEVInjector {
// Compute all the key record sequence first to make sure a mapping is available
let syms = convert_to_sym_array(keys)?;
let records = self.convert_to_record_array(&syms)?;
let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
// First press the keys

View File

@ -8,7 +8,13 @@ use scopeguard::ScopeGuard;
use anyhow::Result;
use thiserror::Error;
use super::{ffi::{xkb_state, xkb_state_key_get_one_sym, xkb_state_key_get_utf8, xkb_state_new, xkb_state_unref, xkb_state_update_key}, keymap::Keymap};
use super::{
ffi::{
xkb_state, xkb_state_key_get_one_sym, xkb_state_key_get_utf8, xkb_state_new, xkb_state_unref,
xkb_state_update_key,
},
keymap::Keymap,
};
pub struct State {
state: *mut xkb_state,

View File

@ -356,4 +356,4 @@ mod tests {
assert!(Key::parse("INVALID").is_none());
assert!(Key::parse("RAW(a)").is_none());
}
}
}

View File

@ -86,7 +86,7 @@ pub struct InjectorCreationOptions {
// Only relevant in X11 Linux systems, use the EVDEV backend instead of X11.
use_evdev: bool,
// Overwrite the list of modifiers to be scanned when
// Overwrite the list of modifiers to be scanned when
// populating the evdev injector lookup maps
evdev_modifiers: Option<Vec<u32>>,
@ -151,4 +151,3 @@ pub fn get_injector(options: InjectorCreationOptions) -> Result<Box<dyn Injector
info!("using EVDEVInjector");
Ok(Box::new(evdev::EVDEVInjector::new(options)?))
}

View File

@ -17,4 +17,4 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
pub mod raw_keys;
pub mod raw_keys;

View File

@ -143,4 +143,4 @@ pub fn convert_to_sym_array(keys: &[Key]) -> Result<Vec<u64>> {
pub enum LinuxRawKeyError {
#[error("missing mapping for key `{0}`")]
MappingFailure(Key),
}
}

View File

@ -27,7 +27,7 @@ use raw_keys::convert_key_to_vkey;
use anyhow::Result;
use thiserror::Error;
use crate::{InjectionOptions, Injector, keys};
use crate::{keys, InjectionOptions, Injector};
#[allow(improper_ctypes)]
#[link(name = "espansoinject", kind = "static")]
@ -72,7 +72,11 @@ impl Injector for MacInjector {
let virtual_keys = Self::convert_to_vk_array(keys)?;
unsafe {
inject_separate_vkeys(virtual_keys.as_ptr(), virtual_keys.len() as i32, options.delay);
inject_separate_vkeys(
virtual_keys.as_ptr(),
virtual_keys.len() as i32,
options.delay,
);
}
Ok(())
@ -82,7 +86,11 @@ impl Injector for MacInjector {
let virtual_keys = Self::convert_to_vk_array(keys)?;
unsafe {
inject_vkeys_combination(virtual_keys.as_ptr(), virtual_keys.len() as i32, options.delay);
inject_vkeys_combination(
virtual_keys.as_ptr(),
virtual_keys.len() as i32,
options.delay,
);
}
Ok(())

View File

@ -19,13 +19,13 @@
mod raw_keys;
use log::{error};
use log::error;
use raw_keys::convert_key_to_vkey;
use anyhow::Result;
use thiserror::Error;
use crate::{InjectionOptions, Injector, keys};
use crate::{keys, InjectionOptions, Injector};
#[allow(improper_ctypes)]
#[link(name = "espansoinject", kind = "static")]
@ -77,7 +77,11 @@ impl Injector for Win32Injector {
}
} else {
unsafe {
inject_separate_vkeys_with_delay(virtual_keys.as_ptr(), virtual_keys.len() as i32, options.delay);
inject_separate_vkeys_with_delay(
virtual_keys.as_ptr(),
virtual_keys.len() as i32,
options.delay,
);
}
}
@ -93,7 +97,11 @@ impl Injector for Win32Injector {
}
} else {
unsafe {
inject_vkeys_combination_with_delay(virtual_keys.as_ptr(), virtual_keys.len() as i32, options.delay);
inject_vkeys_combination_with_delay(
virtual_keys.as_ptr(),
virtual_keys.len() as i32,
options.delay,
);
}
}
@ -113,6 +121,9 @@ mod tests {
#[test]
fn convert_raw_to_virtual_key_array() {
assert_eq!(Win32Injector::convert_to_vk_array(&[keys::Key::Alt, keys::Key::V]).unwrap(), vec![0x12, 0x56]);
assert_eq!(
Win32Injector::convert_to_vk_array(&[keys::Key::Alt, keys::Key::V]).unwrap(),
vec![0x12, 0x56]
);
}
}
}

View File

@ -26,11 +26,15 @@ use std::{
slice,
};
use ffi::{Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay, XDefaultRootWindow, XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XLookupString, XQueryKeymap, XSendEvent, XSync, XTestFakeKeyEvent};
use ffi::{
Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay, XDefaultRootWindow,
XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XLookupString,
XQueryKeymap, XSendEvent, XSync, XTestFakeKeyEvent,
};
use log::error;
use crate::linux::raw_keys::convert_to_sym_array;
use anyhow::Result;
use crate::linux::raw_keys::{convert_to_sym_array};
use thiserror::Error;
use crate::{keys, InjectionOptions, Injector};
@ -174,7 +178,6 @@ impl X11Injector {
modifiers.modifiermap,
(8 * modifiers.max_keypermod) as usize,
)
};
let keycode = modifier_map[(mod_index * modifiers.max_keypermod + mod_key) as usize];
if keycode != 0 {
@ -355,7 +358,7 @@ impl Injector for X11Injector {
fn send_keys(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
let focused_window = self.get_focused_window();
// Compute all the key record sequence first to make sure a mapping is available
let syms = convert_to_sym_array(keys)?;
let records = self.convert_to_record_array(&syms)?;
@ -385,7 +388,7 @@ impl Injector for X11Injector {
// Compute all the key record sequence first to make sure a mapping is available
let syms = convert_to_sym_array(keys)?;
let records = self.convert_to_record_array(&syms)?;
// Render the correct modifier mask for the given sequence
let records = self.render_key_combination(&records);
@ -427,4 +430,4 @@ pub enum X11InjectorError {
#[error("missing record mapping for sym `{0}`")]
SymMappingFailure(u64),
}
}

View File

@ -17,11 +17,11 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
use std::path::Path;
use anyhow::Result;
use serde::{Serialize, de::DeserializeOwned};
use crossbeam::channel::{unbounded, Receiver};
use serde::{de::DeserializeOwned, Serialize};
use std::path::Path;
use thiserror::Error;
use crossbeam::channel::{Receiver, unbounded};
#[cfg(target_os = "windows")]
pub mod windows;
@ -39,7 +39,10 @@ pub trait IPCClient<Event> {
}
#[cfg(not(target_os = "windows"))]
pub fn server<Event: Send + Sync + DeserializeOwned>(id: &str, parent_dir: &Path) -> Result<(impl IPCServer<Event>, Receiver<Event>)> {
pub fn server<Event: Send + Sync + DeserializeOwned>(
id: &str,
parent_dir: &Path,
) -> Result<(impl IPCServer<Event>, Receiver<Event>)> {
let (sender, receiver) = unbounded();
let server = unix::UnixIPCServer::new(id, parent_dir, sender)?;
Ok((server, receiver))
@ -52,7 +55,10 @@ pub fn client<Event: Serialize>(id: &str, parent_dir: &Path) -> Result<impl IPCC
}
#[cfg(target_os = "windows")]
pub fn server<Event: Send + Sync + DeserializeOwned>(id: &str, _: &Path) -> Result<(impl IPCServer<Event>, Receiver<Event>)> {
pub fn server<Event: Send + Sync + DeserializeOwned>(
id: &str,
_: &Path,
) -> Result<(impl IPCServer<Event>, Receiver<Event>)> {
let (sender, receiver) = unbounded();
let server = windows::WinIPCServer::new(id, sender)?;
Ok((server, receiver))
@ -76,7 +82,7 @@ pub enum IPCServerError {
#[cfg(test)]
mod tests {
use super::*;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
enum Event {
@ -93,10 +99,10 @@ mod tests {
// TODO: avoid delay and change the IPC code so that we can wait for the IPC
//std::thread::sleep(std::time::Duration::from_secs(1));
let client = client::<Event>("testespansoipc", &std::env::temp_dir()).unwrap();
client.send(Event::Foo("hello".to_string())).unwrap();
let event = receiver.recv().unwrap();
assert!(matches!(event, Event::Foo(x) if x == "hello"));
@ -108,4 +114,4 @@ mod tests {
let client = client::<Event>("testespansoipc", &std::env::temp_dir()).unwrap();
assert!(client.send(Event::Foo("hello".to_string())).is_err());
}
}
}

View File

@ -22,9 +22,7 @@ use crossbeam::channel::Sender;
use log::{error, info};
use named_pipe::{PipeClient, PipeOptions};
use serde::{de::DeserializeOwned, Serialize};
use std::{
io::{BufReader, Read, Write},
};
use std::io::{BufReader, Read, Write};
use crate::{IPCClient, IPCServer, IPCServerError};
@ -41,10 +39,7 @@ impl<Event> WinIPCServer<Event> {
let options = PipeOptions::new(&pipe_name);
info!(
"binded to named pipe: {}",
pipe_name
);
info!("binded to named pipe: {}", pipe_name);
Ok(Self { options, sender })
}
@ -116,4 +111,3 @@ impl<Event: Serialize> IPCClient<Event> for WinIPCClient {
Ok(())
}
}

View File

@ -1,5 +1,5 @@
use icons::TrayIcon;
use anyhow::Result;
use icons::TrayIcon;
use thiserror::Error;
pub mod event;
@ -48,14 +48,15 @@ pub fn create_ui(options: UIOptions) -> Result<(Box<dyn UIRemote>, Box<dyn UIEve
let (remote, eventloop) = win32::create(win32::Win32UIOptions {
show_icon: options.show_icon,
icon_paths: &options.icon_paths,
notification_icon_path: options.notification_icon_path.ok_or_else(|| UIError::MissingOption("notification icon".to_string()))?,
notification_icon_path: options
.notification_icon_path
.ok_or_else(|| UIError::MissingOption("notification icon".to_string()))?,
})?;
Ok((Box::new(remote), Box::new(eventloop)))
}
#[cfg(target_os = "macos")]
pub fn create_ui(options: UIOptions) -> Result<(Box<dyn UIRemote>, Box<dyn
UIEventLoop>)> {
pub fn create_ui(options: UIOptions) -> Result<(Box<dyn UIRemote>, Box<dyn UIEventLoop>)> {
let (remote, eventloop) = mac::create(mac::MacUIOptions {
show_icon: options.show_icon,
icon_paths: &options.icon_paths,
@ -66,7 +67,9 @@ pub fn create_ui(options: UIOptions) -> Result<(Box<dyn UIRemote>, Box<dyn
#[cfg(target_os = "linux")]
pub fn create_ui(options: UIOptions) -> Result<(Box<dyn UIRemote>, Box<dyn UIEventLoop>)> {
let (remote, eventloop) = linux::create(linux::LinuxUIOptions {
notification_icon_path: options.notification_icon_path.ok_or_else(|| UIError::MissingOption("notification icon".to_string()))?,
notification_icon_path: options
.notification_icon_path
.ok_or_else(|| UIError::MissingOption("notification icon".to_string()))?,
});
Ok((Box::new(remote), Box::new(eventloop)))
}
@ -75,4 +78,4 @@ pub fn create_ui(options: UIOptions) -> Result<(Box<dyn UIRemote>, Box<dyn UIEve
pub enum UIError {
#[error("missing required option for ui: `{0}`")]
MissingOption(String),
}
}

View File

@ -1,5 +1,5 @@
use log::error;
use anyhow::Result;
use log::error;
use notify_rust::Notification;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};

View File

@ -20,11 +20,11 @@
use std::{cmp::min, collections::HashMap, ffi::CString, os::raw::c_char, thread::ThreadId};
use anyhow::Result;
use thiserror::Error;
use lazycell::LazyCell;
use log::{error, trace};
use thiserror::Error;
use crate::{UIEventLoop, UIRemote, event::UIEvent, icons::TrayIcon, menu::Menu};
use crate::{event::UIEvent, icons::TrayIcon, menu::Menu, UIEventLoop, UIRemote};
// IMPORTANT: if you change these, also edit the native.h file.
const MAX_FILE_PATH: usize = 1024;
@ -105,8 +105,6 @@ impl MacEventLoop {
_init_thread_id: LazyCell::new(),
}
}
}
impl UIEventLoop for MacEventLoop {
@ -133,7 +131,7 @@ impl UIEventLoop for MacEventLoop {
._init_thread_id
.fill(std::thread::current().id())
.expect("Unable to set initialization thread id");
Ok(())
}
@ -147,7 +145,7 @@ impl UIEventLoop for MacEventLoop {
if self._event_callback.fill(event_callback).is_err() {
error!("Unable to set MacEventLoop callback");
return Err(MacUIError::InternalError().into())
return Err(MacUIError::InternalError().into());
}
extern "C" fn callback(_self: *mut MacEventLoop, event: RawUIEvent) {
@ -165,7 +163,7 @@ impl UIEventLoop for MacEventLoop {
if error_code <= 0 {
error!("MacEventLoop exited with <= 0 code");
return Err(MacUIError::InternalError().into())
return Err(MacUIError::InternalError().into());
}
Ok(())
@ -180,7 +178,7 @@ pub struct MacRemote {
impl MacRemote {
pub(crate) fn new(icon_indexes: HashMap<TrayIcon, usize>) -> Self {
Self { icon_indexes }
}
}
}
impl UIRemote for MacRemote {
@ -242,4 +240,4 @@ impl From<RawUIEvent> for Option<UIEvent> {
pub enum MacUIError {
#[error("internal error")]
InternalError(),
}
}

View File

@ -29,13 +29,13 @@ use std::{
thread::ThreadId,
};
use anyhow::Result;
use lazycell::LazyCell;
use log::{error, trace};
use widestring::WideCString;
use anyhow::Result;
use thiserror::Error;
use widestring::WideCString;
use crate::{UIEventCallback, UIEventLoop, UIRemote, event::UIEvent, icons::TrayIcon, menu::Menu};
use crate::{event::UIEvent, icons::TrayIcon, menu::Menu, UIEventCallback, UIEventLoop, UIRemote};
// IMPORTANT: if you change these, also edit the native.h file.
const MAX_FILE_PATH: usize = 260;
@ -154,8 +154,7 @@ impl UIEventLoop for Win32EventLoop {
let mut icon_paths: [[u16; MAX_FILE_PATH]; MAX_ICON_COUNT] =
[[0; MAX_FILE_PATH]; MAX_ICON_COUNT];
for (i, icon_path) in icon_paths.iter_mut().enumerate().take(self.icons.len()) {
let wide_path =
WideCString::from_str(&self.icons[i])?;
let wide_path = WideCString::from_str(&self.icons[i])?;
let len = min(wide_path.len(), MAX_FILE_PATH - 1);
icon_path[0..len].clone_from_slice(&wide_path.as_slice()[..len]);
}
@ -178,11 +177,31 @@ impl UIEventLoop for Win32EventLoop {
if handle.is_null() {
return match error_code {
-1 => Err(Win32UIError::EventLoopInitError("Unable to initialize Win32EventLoop, error registering window class".to_string()).into()),
-2 => Err(Win32UIError::EventLoopInitError("Unable to initialize Win32EventLoop, error creating window".to_string()).into()),
-3 => Err(Win32UIError::EventLoopInitError("Unable to initialize Win32EventLoop, initializing notifications".to_string()).into()),
_ => Err(Win32UIError::EventLoopInitError("Unable to initialize Win32EventLoop, unknown error".to_string()).into()),
}
-1 => Err(
Win32UIError::EventLoopInitError(
"Unable to initialize Win32EventLoop, error registering window class".to_string(),
)
.into(),
),
-2 => Err(
Win32UIError::EventLoopInitError(
"Unable to initialize Win32EventLoop, error creating window".to_string(),
)
.into(),
),
-3 => Err(
Win32UIError::EventLoopInitError(
"Unable to initialize Win32EventLoop, initializing notifications".to_string(),
)
.into(),
),
_ => Err(
Win32UIError::EventLoopInitError(
"Unable to initialize Win32EventLoop, unknown error".to_string(),
)
.into(),
),
};
}
self.handle.store(handle, Ordering::Release);
@ -192,7 +211,7 @@ impl UIEventLoop for Win32EventLoop {
._init_thread_id
.fill(std::thread::current().id())
.expect("Unable to set initialization thread id");
Ok(())
}
@ -207,12 +226,12 @@ impl UIEventLoop for Win32EventLoop {
let window_handle = self.handle.load(Ordering::Acquire);
if window_handle.is_null() {
error!("Attempt to run Win32EventLoop on a null window handle");
return Err(Win32UIError::InvalidHandle().into())
return Err(Win32UIError::InvalidHandle().into());
}
if self._event_callback.fill(event_callback).is_err() {
error!("Unable to set Win32EventLoop callback");
return Err(Win32UIError::InternalError().into())
return Err(Win32UIError::InternalError().into());
}
extern "C" fn callback(_self: *mut Win32EventLoop, event: RawUIEvent) {
@ -230,7 +249,7 @@ impl UIEventLoop for Win32EventLoop {
if error_code <= 0 {
error!("Win32EventLoop exited with <= 0 code");
return Err(Win32UIError::InternalError().into())
return Err(Win32UIError::InternalError().into());
}
Ok(())

View File

@ -1,7 +1,10 @@
use std::time::Duration;
use espanso_detect::{event::{InputEvent, Status}, get_source};
use espanso_inject::{get_injector, Injector, keys};
use espanso_detect::{
event::{InputEvent, Status},
get_source,
};
use espanso_inject::{get_injector, keys, Injector};
use espanso_ui::{event::UIEvent::*, icons::TrayIcon, menu::*};
use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode};
@ -49,9 +52,12 @@ fn main() {
// icon_paths: &icon_paths,
// });
let (remote, mut eventloop) = espanso_ui::create_ui(espanso_ui::UIOptions {
notification_icon_path: Some(r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png".to_string()),
notification_icon_path: Some(
r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png".to_string(),
),
..Default::default()
}).unwrap();
})
.unwrap();
eventloop.initialize().unwrap();
@ -59,21 +65,25 @@ fn main() {
let injector = get_injector(Default::default()).unwrap();
let mut source = get_source(Default::default()).unwrap();
source.initialize().unwrap();
source.eventloop(Box::new(move |event: InputEvent| {
println!("ev {:?}", event);
match event {
InputEvent::Mouse(_) => {}
InputEvent::Keyboard(evt) => {
if evt.key == espanso_detect::event::Key::Escape && evt.status == Status::Released {
//remote.update_tray_icon(espanso_ui::icons::TrayIcon::Disabled);
//remote.show_notification("Espanso is running!");
injector.send_string("Hey guys! @", Default::default()).expect("error");
//std::thread::sleep(std::time::Duration::from_secs(2));
//injector.send_key_combination(&[keys::Key::Control, keys::Key::V], Default::default()).unwrap();
source
.eventloop(Box::new(move |event: InputEvent| {
println!("ev {:?}", event);
match event {
InputEvent::Mouse(_) => {}
InputEvent::Keyboard(evt) => {
if evt.key == espanso_detect::event::Key::Escape && evt.status == Status::Released {
//remote.update_tray_icon(espanso_ui::icons::TrayIcon::Disabled);
//remote.show_notification("Espanso is running!");
injector
.send_string("Hey guys! @", Default::default())
.expect("error");
//std::thread::sleep(std::time::Duration::from_secs(2));
//injector.send_key_combination(&[keys::Key::Control, keys::Key::V], Default::default()).unwrap();
}
}
}
}
})).unwrap();
}))
.unwrap();
});
eventloop.run(Box::new(move |event| {