Add license header and formatting
This commit is contained in:
		
							parent
							
								
									4143caff3d
								
							
						
					
					
						commit
						e8881d0faf
					
				| 
						 | 
				
			
			@ -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),
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
                  );
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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());
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
          }],
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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());
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -356,4 +356,4 @@ mod tests {
 | 
			
		|||
    assert!(Key::parse("INVALID").is_none());
 | 
			
		||||
    assert!(Key::parse("RAW(a)").is_none());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)?))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,4 +17,4 @@
 | 
			
		|||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
pub mod raw_keys;
 | 
			
		||||
pub mod raw_keys;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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]
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(),
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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| {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user