Add context capture and rules
This commit is contained in:
parent
34ecdcbfde
commit
0e79edfa20
7 changed files with 247 additions and 80 deletions
93
src/context.py
Normal file
93
src/context.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Context:
|
||||
window_id: int
|
||||
app_id: str
|
||||
klass: str
|
||||
instance: str
|
||||
title: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContextRule:
|
||||
match: dict
|
||||
ai_prompt_file: str | None = None
|
||||
ai_enabled: bool | None = None
|
||||
injection_backend: str | None = None
|
||||
tag: str | None = None
|
||||
|
||||
|
||||
class ContextProvider:
|
||||
def capture(self) -> Context:
|
||||
raise NotImplementedError
|
||||
|
||||
def is_same_focus(self, ctx: Context) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class I3Provider(ContextProvider):
|
||||
def __init__(self):
|
||||
import i3ipc
|
||||
|
||||
self.i3 = i3ipc.Connection()
|
||||
|
||||
def _focused(self):
|
||||
node = self.i3.get_tree().find_focused()
|
||||
if node is None:
|
||||
raise RuntimeError("no focused window")
|
||||
return node
|
||||
|
||||
def capture(self) -> Context:
|
||||
node = self._focused()
|
||||
props = node.window_properties or {}
|
||||
return Context(
|
||||
window_id=node.id,
|
||||
app_id=node.app_id or "",
|
||||
klass=props.get("class") or "",
|
||||
instance=props.get("instance") or "",
|
||||
title=node.name or "",
|
||||
)
|
||||
|
||||
def is_same_focus(self, ctx: Context) -> bool:
|
||||
node = self._focused()
|
||||
return node.id == ctx.window_id
|
||||
|
||||
|
||||
def _match_text(val: str, needle: str | None) -> bool:
|
||||
if not needle:
|
||||
return True
|
||||
return val == needle
|
||||
|
||||
|
||||
def _match_title_contains(title: str, needle: str | None) -> bool:
|
||||
if not needle:
|
||||
return True
|
||||
return needle.lower() in title.lower()
|
||||
|
||||
|
||||
def _match_title_regex(title: str, pattern: str | None) -> bool:
|
||||
if not pattern:
|
||||
return True
|
||||
return re.search(pattern, title) is not None
|
||||
|
||||
|
||||
def match_rule(ctx: Context, rules: list[ContextRule]) -> ContextRule | None:
|
||||
for rule in rules:
|
||||
match = rule.match or {}
|
||||
if not _match_text(ctx.app_id, match.get("app_id")):
|
||||
continue
|
||||
if not _match_text(ctx.klass, match.get("class")):
|
||||
continue
|
||||
if not _match_text(ctx.instance, match.get("instance")):
|
||||
continue
|
||||
if not _match_title_contains(ctx.title, match.get("title_contains")):
|
||||
continue
|
||||
if not _match_title_regex(ctx.title, match.get("title_regex")):
|
||||
continue
|
||||
return rule
|
||||
return None
|
||||
Loading…
Add table
Add a link
Reference in a new issue