Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,34 @@ that it might not get the exact version of the dependencies due to not reading t

In order for this watcher to be available in the UI, you'll need to have a Away From Computer (afk) watcher running alongside it.

## Title Cleaning Rules

You can configure regex-based title cleaning rules to modify window titles before they are sent to the server. This is useful for removing application-specific suffixes, file paths, or other unwanted information from window titles.

### Configuration

Add title cleaning rules to your `~/.config/activitywatch/aw-watcher-window.toml` file:

```toml
# Title cleaning rules - apply regex replacements to window titles for specific apps
[[title_cleaning_rules.rules]]
app = "firefox"
pattern = "( - Mozilla Firefox)$"
replacement = ""

[[title_cleaning_rules.rules]]
app = "code"
pattern = " - Visual Studio Code$"
replacement = ""
```

Each rule consists of:
- `app`: The application name (case-insensitive match)
- `pattern`: Regular expression pattern to match in the window title
- `replacement`: String to replace matches with (can be empty to remove)

See `example-config.toml` for more examples.

### Note to macOS users

To log current window title the terminal needs access to macOS accessibility API.
Expand Down
28 changes: 26 additions & 2 deletions aw_watcher_window/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,39 @@
exclude_titles = []
poll_time = 1.0
strategy_macos = "swift"

# Title cleaning rules - apply regex replacements to window titles for specific apps
# [[title_cleaning_rules.rules]]
# app = "firefox"
# pattern = "( - Mozilla Firefox)$"
# replacement = ""
""".strip()


def load_config():
return load_config_toml("aw-watcher-window", default_config)["aw-watcher-window"]
return load_config_toml("aw-watcher-window", default_config)


def get_title_cleaning_rules():
"""Load title cleaning rules from configuration"""
config = load_config_toml("aw-watcher-window", default_config)
rules = []

# Check if title_cleaning_rules section exists
if "title_cleaning_rules" in config and "rules" in config["title_cleaning_rules"]:
for rule in config["title_cleaning_rules"]["rules"]:
if "app" in rule and "pattern" in rule:
rules.append({
"app": rule["app"],
"pattern": rule["pattern"],
"replacement": rule.get("replacement", "")
})

return rules


def parse_args():
config = load_config()
config = load_config()["aw-watcher-window"]

default_poll_time = config["poll_time"]
default_exclude_title = config["exclude_title"]
Expand Down
23 changes: 22 additions & 1 deletion aw_watcher_window/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from aw_core.log import setup_logging
from aw_core.models import Event

from .config import parse_args
from .config import parse_args, get_title_cleaning_rules
from .exceptions import FatalError
from .lib import get_current_window
from .macos_permissions import background_ensure_permissions
Expand Down Expand Up @@ -40,6 +40,23 @@ def try_compile_title_regex(title):
exit(1)


def clean_window_title(app, title):
"""Apply regex replacement rules to window title based on app name"""
try:
rules = get_title_cleaning_rules()
for rule in rules:
if rule["app"].lower() == app.lower():
try:
title = re.sub(rule["pattern"], rule["replacement"], title)
logger.debug(f"Applied title cleaning rule for {app}: {rule['pattern']} -> {rule['replacement']}")
except re.error:
logger.error(f"Invalid regex pattern in title cleaning rule for {app}: {rule['pattern']}")
return title
except Exception as e:
logger.error(f"Error applying title cleaning rules: {e}")
return title


def main():
args = parse_args()

Expand Down Expand Up @@ -144,6 +161,10 @@ def heartbeat_loop(
if current_window is None:
logger.debug("Unable to fetch window, trying again on next poll")
else:
# Apply title cleaning rules before other processing
if "app" in current_window and "title" in current_window:
current_window["title"] = clean_window_title(current_window["app"], current_window["title"])

for pattern in exclude_titles:
if pattern.search(current_window["title"]):
current_window["title"] = "excluded"
Expand Down
41 changes: 41 additions & 0 deletions example-config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Example configuration file for aw-watcher-window with title cleaning rules
# Save this as ~/.config/activitywatch/aw-watcher-window.toml

[aw-watcher-window]
exclude_title = false
exclude_titles = []
poll_time = 1.0
strategy_macos = "swift"

# Title cleaning rules - apply regex replacements to window titles for specific apps
[[title_cleaning_rules.rules]]
app = "firefox"
pattern = "( - Mozilla Firefox)$"
replacement = ""

[[title_cleaning_rules.rules]]
app = "google-chrome"
pattern = "( - Google Chrome)$"
replacement = ""

[[title_cleaning_rules.rules]]
app = "code"
pattern = " - Visual Studio Code$"
replacement = ""

[[title_cleaning_rules.rules]]
app = "sublime_text"
pattern = " \\(.*\\) - Sublime Text$"
replacement = ""

# Example: Remove file paths from terminal titles
[[title_cleaning_rules.rules]]
app = "gnome-terminal"
pattern = "^.*: (.*)$"
replacement = "\\1"

# Example: Clean browser tab information
[[title_cleaning_rules.rules]]
app = "firefox"
pattern = "^(.*) — Mozilla Firefox$"
replacement = "\\1"