mirror of
https://github.com/2e3s/awatcher.git
synced 2025-06-06 11:35:46 +00:00
Support captures in replacements
This commit is contained in:
parent
59b2c88c65
commit
78c66181b1
20
README.md
20
README.md
@ -5,8 +5,9 @@ Awatcher is a window activity and idle watcher with an optional tray and UI for
|
|||||||
The goal is to compensate the fragmentation of desktop environments on Linux by supporting all reportable environments,
|
The goal is to compensate the fragmentation of desktop environments on Linux by supporting all reportable environments,
|
||||||
to add more flexibility to reports with filters, and to have better UX with the distribution by a single executable.
|
to add more flexibility to reports with filters, and to have better UX with the distribution by a single executable.
|
||||||
|
|
||||||
The foundation is taken from [ActivityWatch](https://github.com/ActivityWatch), which includes the server and web UI.
|
The foundation is [ActivityWatch](https://github.com/ActivityWatch), which includes the server and web UI.
|
||||||
The unbundled watcher can replace the original idle and active window watchers in the original distribution if necessary.
|
The unbundled watcher is supposed to replace the original idle and active window watchers from the original distribution.
|
||||||
|
The bundled executable can be used independently as it contains the server, UI and tray.
|
||||||
|
|
||||||
The crate also provides a library with watchers which can send the data to the server.
|
The crate also provides a library with watchers which can send the data to the server.
|
||||||
|
|
||||||
@ -109,10 +110,21 @@ Matches are case sensitive regular expressions between implici ^ and $:
|
|||||||
- `.*` matches any number of any characters
|
- `.*` matches any number of any characters
|
||||||
- `.+` matches 1 or more any characters.
|
- `.+` matches 1 or more any characters.
|
||||||
- `word` is an exact match.
|
- `word` is an exact match.
|
||||||
- Use escapes to match special characters, e.g. `org\.kde\.Dolpin`
|
- Use escapes `\` to match special characters, e.g. `org\.kde\.Dolpin`
|
||||||
|
|
||||||
|
The replacements in filters also support regexp captures.
|
||||||
|
A captures takes a string in parentheses from the match and replaces `$N` in the replacement, where `N` is the number of parentheses.
|
||||||
|
Example filter to remove the changed file indicator in Visual Studio Code:
|
||||||
|
```toml
|
||||||
|
[[awatcher.filters]]
|
||||||
|
match-app-id = "code"
|
||||||
|
match-title = "● (.*)"
|
||||||
|
# Inserts the content within 1st parentheses, this can be in any form, e.g. "App $1 - $2/$3"
|
||||||
|
replace-title = "$1"
|
||||||
|
```
|
||||||
|
|
||||||
Run the command with "debug" or "trace" verbosity and without reporting to server in the terminal
|
Run the command with "debug" or "trace" verbosity and without reporting to server in the terminal
|
||||||
to see what application names and titles are reported to the server.
|
to see what application names and titles are reported to the server.
|
||||||
```
|
```
|
||||||
$ awatcher -vvv --no-server
|
$ awatcher -vvv --no-server
|
||||||
```
|
```
|
||||||
|
@ -7,6 +7,48 @@ use crate::config::defaults;
|
|||||||
|
|
||||||
use super::filters::Filter;
|
use super::filters::Filter;
|
||||||
|
|
||||||
|
pub fn default_config() -> String {
|
||||||
|
format!(
|
||||||
|
r#"# The commented values are the defaults on the file creation
|
||||||
|
[server]
|
||||||
|
# port = {}
|
||||||
|
# host = "{}"
|
||||||
|
|
||||||
|
[awatcher]
|
||||||
|
# idle-timeout-seconds={}
|
||||||
|
# poll-time-idle-seconds={}
|
||||||
|
# poll-time-window-seconds={}
|
||||||
|
|
||||||
|
# Add as many filters as needed. The first matching filter stops the replacement.
|
||||||
|
# There should be at least 1 match field, and at least 1 replace field.
|
||||||
|
# Matches are case sensitive regular expressions between implici ^ and $, e.g.
|
||||||
|
# - "." matches 1 any character
|
||||||
|
# - ".*" matches any number of any characters
|
||||||
|
# - ".+" matches 1 or more any characters.
|
||||||
|
# - "word" is an exact match.
|
||||||
|
# [[awatcher.filters]]
|
||||||
|
# match-app-id = "navigator"
|
||||||
|
# match-title = ".*Firefox.*"
|
||||||
|
# replace-app-id = "firefox"
|
||||||
|
# replace-title = "Unknown"
|
||||||
|
|
||||||
|
# Use captures for app-id or title in the regular form to use parts of the original text
|
||||||
|
# (parentheses for a capture, $1, $2 etc for each capture).
|
||||||
|
# The example rule removes the changed file indicator from the title in Visual Studio Code:
|
||||||
|
# "● file_config.rs - awatcher - Visual Studio Code" to "file_config.rs - awatcher - Visual Studio Code".
|
||||||
|
# [[awatcher.filters]]
|
||||||
|
# match-app-id = "code"
|
||||||
|
# match-title = "● (.*)"
|
||||||
|
# replace-title = "$1"
|
||||||
|
"#,
|
||||||
|
defaults::port(),
|
||||||
|
defaults::host(),
|
||||||
|
defaults::idle_timeout_seconds(),
|
||||||
|
defaults::poll_time_idle_seconds(),
|
||||||
|
defaults::poll_time_window_seconds(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, DefaultFromSerde)]
|
#[derive(Deserialize, DefaultFromSerde)]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
#[serde(default = "defaults::port")]
|
#[serde(default = "defaults::port")]
|
||||||
@ -69,36 +111,7 @@ impl FileConfig {
|
|||||||
|
|
||||||
toml::from_str(&config_content)?
|
toml::from_str(&config_content)?
|
||||||
} else {
|
} else {
|
||||||
let config = format!(
|
let config = default_config();
|
||||||
r#"# The commented values are the defaults on the file creation
|
|
||||||
[server]
|
|
||||||
# port = {}
|
|
||||||
# host = "{}"
|
|
||||||
|
|
||||||
[awatcher]
|
|
||||||
# idle-timeout-seconds={}
|
|
||||||
# poll-time-idle-seconds={}
|
|
||||||
# poll-time-window-seconds={}
|
|
||||||
|
|
||||||
# Add as many filters as needed. The first matching filter stops the replacement.
|
|
||||||
# There should be at least 1 match field, and at least 1 replace field.
|
|
||||||
# Matches are case sensitive regular expressions between implici ^ and $, e.g.
|
|
||||||
# - "." matches 1 any character
|
|
||||||
# - ".*" matches any number of any characters
|
|
||||||
# - ".+" matches 1 or more any characters.
|
|
||||||
# - "word" is an exact match.
|
|
||||||
# [[awatcher.filters]]
|
|
||||||
# match-app-id = "navigator"
|
|
||||||
# match-title = ".*Firefox.*"
|
|
||||||
# replace-app-id = "firefox"
|
|
||||||
# replace-title = "Unknown"
|
|
||||||
"#,
|
|
||||||
defaults::port(),
|
|
||||||
defaults::host(),
|
|
||||||
defaults::idle_timeout_seconds(),
|
|
||||||
defaults::poll_time_idle_seconds(),
|
|
||||||
defaults::poll_time_window_seconds(),
|
|
||||||
);
|
|
||||||
let error = std::fs::create_dir(config_path.parent().unwrap());
|
let error = std::fs::create_dir(config_path.parent().unwrap());
|
||||||
if let Err(e) = error {
|
if let Err(e) = error {
|
||||||
if e.kind() != ErrorKind::AlreadyExists {
|
if e.kind() != ErrorKind::AlreadyExists {
|
||||||
|
@ -39,8 +39,10 @@ pub struct Replacement {
|
|||||||
|
|
||||||
impl Filter {
|
impl Filter {
|
||||||
fn is_valid(&self) -> bool {
|
fn is_valid(&self) -> bool {
|
||||||
(self.match_app_id.is_some() || self.match_title.is_some())
|
let is_match_set = self.match_app_id.is_some() || self.match_title.is_some();
|
||||||
&& (self.replace_app_id.is_some() || self.replace_title.is_some())
|
let is_replacement_set = self.replace_app_id.is_some() || self.replace_title.is_some();
|
||||||
|
|
||||||
|
is_match_set && is_replacement_set
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_match(&self, app_id: &str, title: &str) -> bool {
|
fn is_match(&self, app_id: &str, title: &str) -> bool {
|
||||||
@ -58,22 +60,29 @@ impl Filter {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn replace(regex: &Option<Regex>, source: &str, replacement: &str) -> String {
|
||||||
|
if let Some(regex) = regex {
|
||||||
|
// Avoid using the more expensive regexp replacements when unnecessary.
|
||||||
|
if regex.captures_len() > 1 {
|
||||||
|
return regex.replace(source, replacement).to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replacement.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn replacement(&self, app_id: &str, title: &str) -> Option<Replacement> {
|
pub fn replacement(&self, app_id: &str, title: &str) -> Option<Replacement> {
|
||||||
if !self.is_valid() {
|
if !self.is_valid() || !self.is_match(app_id, title) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_match(app_id, title) {
|
let mut replacement = Replacement::default();
|
||||||
let mut replacement = Replacement::default();
|
if let Some(new_app_id) = &self.replace_app_id {
|
||||||
if let Some(new_app_id) = &self.replace_app_id {
|
replacement.replace_app_id =
|
||||||
replacement.replace_app_id = Some(new_app_id.to_string());
|
Some(Self::replace(&self.match_app_id, app_id, new_app_id));
|
||||||
}
|
|
||||||
if let Some(new_title) = &self.replace_title {
|
|
||||||
replacement.replace_title = Some(new_title.to_string());
|
|
||||||
}
|
|
||||||
Some(replacement)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
if let Some(new_title) = &self.replace_title {
|
||||||
|
replacement.replace_title = Some(Self::replace(&self.match_title, title, new_title));
|
||||||
|
}
|
||||||
|
Some(replacement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,11 @@ impl ReportClient {
|
|||||||
} else {
|
} else {
|
||||||
title.to_string()
|
title.to_string()
|
||||||
};
|
};
|
||||||
trace!("Reporting app_id: {}, title: {}", app_id, title);
|
trace!(
|
||||||
|
"Reporting app_id: {}, title: {}",
|
||||||
|
inserted_app_id,
|
||||||
|
inserted_title
|
||||||
|
);
|
||||||
data.insert("app".to_string(), Value::String(inserted_app_id));
|
data.insert("app".to_string(), Value::String(inserted_app_id));
|
||||||
data.insert("title".to_string(), Value::String(inserted_title));
|
data.insert("title".to_string(), Value::String(inserted_title));
|
||||||
let event = AwEvent {
|
let event = AwEvent {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user