Reformat code and add option to add keywords
This commit is contained in:
@@ -6,6 +6,7 @@ class AuthExternal(Authenticator):
|
|||||||
:class:`MessageBus <dbus_next.message_bus.BaseMessageBus>`.
|
:class:`MessageBus <dbus_next.message_bus.BaseMessageBus>`.
|
||||||
:sealso: https://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol
|
:sealso: https://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, user_uid):
|
def __init__(self, user_uid):
|
||||||
self.user_uid = user_uid
|
self.user_uid = user_uid
|
||||||
self.negotiate_unix_fd = False
|
self.negotiate_unix_fd = False
|
||||||
@@ -14,7 +15,7 @@ class AuthExternal(Authenticator):
|
|||||||
def _authentication_start(self, negotiate_unix_fd=False) -> str:
|
def _authentication_start(self, negotiate_unix_fd=False) -> str:
|
||||||
self.negotiate_unix_fd = negotiate_unix_fd
|
self.negotiate_unix_fd = negotiate_unix_fd
|
||||||
hex_uid = str(self.user_uid).encode().hex()
|
hex_uid = str(self.user_uid).encode().hex()
|
||||||
return f'AUTH EXTERNAL {hex_uid}'
|
return f"AUTH EXTERNAL {hex_uid}"
|
||||||
|
|
||||||
def _receive_line(self, line: str):
|
def _receive_line(self, line: str):
|
||||||
response, args = _AuthResponse.parse(line)
|
response, args = _AuthResponse.parse(line)
|
||||||
@@ -29,5 +30,4 @@ class AuthExternal(Authenticator):
|
|||||||
if response is _AuthResponse.AGREE_UNIX_FD:
|
if response is _AuthResponse.AGREE_UNIX_FD:
|
||||||
return "BEGIN"
|
return "BEGIN"
|
||||||
|
|
||||||
raise AuthError(f'authentication failed: {response.value}: {args}')
|
raise AuthError(f"authentication failed: {response.value}: {args}")
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import asyncio
|
import time
|
||||||
from dbus_next.aio import MessageBus
|
from dbus_next.aio import MessageBus
|
||||||
from dbus_next import BusType
|
from dbus_next import BusType
|
||||||
from dbus_next.errors import DBusError
|
from dbus_next.errors import DBusError
|
||||||
@@ -20,7 +20,6 @@ async def get_dbus_interface():
|
|||||||
|
|
||||||
|
|
||||||
async def run(args: Namespace, config: Config):
|
async def run(args: Namespace, config: Config):
|
||||||
|
|
||||||
if args.evaluate:
|
if args.evaluate:
|
||||||
evaluate(config)
|
evaluate(config)
|
||||||
return
|
return
|
||||||
@@ -35,6 +34,8 @@ async def run(args: Namespace, config: Config):
|
|||||||
reply = await interface.call_stop()
|
reply = await interface.call_stop()
|
||||||
elif args.pause:
|
elif args.pause:
|
||||||
reply = await interface.call_pause()
|
reply = await interface.call_pause()
|
||||||
|
elif args.block is not None:
|
||||||
|
reply = await interface.call_block(args.block)
|
||||||
elif args.intention is not None:
|
elif args.intention is not None:
|
||||||
reply = await interface.call_intention(args.intention)
|
reply = await interface.call_intention(args.intention)
|
||||||
elif args.unpause:
|
elif args.unpause:
|
||||||
|
|||||||
@@ -36,18 +36,33 @@ class Config(BaseModel):
|
|||||||
config = cls(**config_dict)
|
config = cls(**config_dict)
|
||||||
config.config_file = Path(config_file)
|
config.config_file = Path(config_file)
|
||||||
# Expand the paths for the log files
|
# Expand the paths for the log files
|
||||||
config.window_log_file = Path(os.path.expanduser(config.window_log_file))
|
config.window_log_file = Path(
|
||||||
config.daemon_log_file = Path(os.path.expanduser(config.daemon_log_file))
|
os.path.expanduser(config.window_log_file))
|
||||||
config.client_log_file = Path(os.path.expanduser(config.client_log_file))
|
config.daemon_log_file = Path(
|
||||||
|
os.path.expanduser(config.daemon_log_file))
|
||||||
|
config.client_log_file = Path(
|
||||||
|
os.path.expanduser(config.client_log_file))
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
def save(self) -> None:
|
||||||
|
config_file = self.config_file
|
||||||
|
config_dict = self.dict()
|
||||||
|
|
||||||
|
# convert Path objects to strings
|
||||||
|
for key, value in config_dict.items():
|
||||||
|
if isinstance(value, Path):
|
||||||
|
config_dict[key] = str(value)
|
||||||
|
|
||||||
|
with open(config_file, "w") as f:
|
||||||
|
yaml.safe_dump(config_dict, f)
|
||||||
|
|
||||||
|
|
||||||
class State(BaseModel):
|
class State(BaseModel):
|
||||||
active_blackblocks: List[Block] = []
|
active_blackblocks: List[Block] = []
|
||||||
active_whiteblocks: List[Block] = []
|
active_whiteblocks: List[Block] = []
|
||||||
inactive_blackblocks: List[Block] = []
|
inactive_blackblocks: List[Block] = []
|
||||||
pause: bool = False
|
pause: bool = False
|
||||||
intention: str = ''
|
intention: str = ""
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
extra = "forbid"
|
extra = "forbid"
|
||||||
|
|||||||
@@ -4,23 +4,21 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import antidrift.xwindow as xwindow
|
import antidrift.xwindow as xwindow
|
||||||
from antidrift.xwindow import XWindow
|
from antidrift.xwindow import XWindow
|
||||||
from antidrift.config import Config, State, Block
|
from antidrift.config import Config, State, Block
|
||||||
from antidrift.auth import AuthExternal
|
from antidrift.auth import AuthExternal
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from dbus_next.aio import MessageBus
|
from dbus_next.aio import MessageBus
|
||||||
from dbus_next.service import ServiceInterface, method
|
from dbus_next.service import ServiceInterface, method
|
||||||
from dbus_next import Variant, BusType
|
from dbus_next import BusType
|
||||||
|
|
||||||
BUS_NAME = "com.antidrift"
|
BUS_NAME = "com.antidrift"
|
||||||
IFACE = "com.antidrift"
|
IFACE = "com.antidrift"
|
||||||
OPATH = "/com/antidrift"
|
OPATH = "/com/antidrift"
|
||||||
|
s = "no pyflakes warning"
|
||||||
|
|
||||||
|
|
||||||
class AntiDriftDaemon(ServiceInterface):
|
class AntiDriftDaemon(ServiceInterface):
|
||||||
@@ -29,7 +27,8 @@ class AntiDriftDaemon(ServiceInterface):
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.reset_block_state()
|
self.reset_block_state()
|
||||||
self.enforce_count = 0
|
self.enforce_count = 0
|
||||||
self.enforce_value = int(config.enforce_delay_ms / config.polling_cycle_ms)
|
self.enforce_value = int(
|
||||||
|
config.enforce_delay_ms / config.polling_cycle_ms)
|
||||||
|
|
||||||
async def init_bus(self):
|
async def init_bus(self):
|
||||||
"""
|
"""
|
||||||
@@ -39,13 +38,15 @@ class AntiDriftDaemon(ServiceInterface):
|
|||||||
the original effective UID to maintain the appropriate privilege
|
the original effective UID to maintain the appropriate privilege
|
||||||
levels.
|
levels.
|
||||||
"""
|
"""
|
||||||
user_name = os.environ.get("SUDO_USER", pwd.getpwuid(os.getuid()).pw_name)
|
user_name = os.environ.get(
|
||||||
|
"SUDO_USER", pwd.getpwuid(os.getuid()).pw_name)
|
||||||
user_uid = pwd.getpwnam(user_name).pw_uid
|
user_uid = pwd.getpwnam(user_name).pw_uid
|
||||||
euid = os.geteuid()
|
euid = os.geteuid()
|
||||||
os.seteuid(user_uid)
|
os.seteuid(user_uid)
|
||||||
auth = AuthExternal(user_uid)
|
auth = AuthExternal(user_uid)
|
||||||
bus_address = f"unix:path=/run/user/{user_uid}/bus"
|
bus_address = f"unix:path=/run/user/{user_uid}/bus"
|
||||||
bus = MessageBus(bus_address=bus_address, bus_type=BusType.SESSION, auth=auth)
|
bus = MessageBus(bus_address=bus_address,
|
||||||
|
bus_type=BusType.SESSION, auth=auth)
|
||||||
await bus.connect()
|
await bus.connect()
|
||||||
bus.export(OPATH, self)
|
bus.export(OPATH, self)
|
||||||
await bus.request_name(BUS_NAME)
|
await bus.request_name(BUS_NAME)
|
||||||
@@ -53,7 +54,7 @@ class AntiDriftDaemon(ServiceInterface):
|
|||||||
return bus
|
return bus
|
||||||
|
|
||||||
async def run(self, debug: bool = False):
|
async def run(self, debug: bool = False):
|
||||||
bus = await self.init_bus()
|
_ = await self.init_bus()
|
||||||
|
|
||||||
async def _enforce():
|
async def _enforce():
|
||||||
while True:
|
while True:
|
||||||
@@ -73,7 +74,7 @@ class AntiDriftDaemon(ServiceInterface):
|
|||||||
asyncio.create_task(_enforce())
|
asyncio.create_task(_enforce())
|
||||||
asyncio.create_task(_log())
|
asyncio.create_task(_log())
|
||||||
|
|
||||||
xwindow.notify(f"AntiDrift running.")
|
xwindow.notify("✅ Antidrift running.")
|
||||||
stop = asyncio.Event()
|
stop = asyncio.Event()
|
||||||
await stop.wait()
|
await stop.wait()
|
||||||
|
|
||||||
@@ -85,13 +86,14 @@ class AntiDriftDaemon(ServiceInterface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def start(self, whiteblocks: 'as') -> 's':
|
def start(self, whiteblocks: "as") -> "s":
|
||||||
self.reset_block_state()
|
self.reset_block_state()
|
||||||
all_whiteblocks = {wb.name: wb for wb in self.config.whiteblocks}
|
all_whiteblocks = {wb.name: wb for wb in self.config.whiteblocks}
|
||||||
success_wbs, fail_blocks = [], []
|
success_wbs, fail_blocks = [], []
|
||||||
for block_name in whiteblocks:
|
for block_name in whiteblocks:
|
||||||
if block_name in all_whiteblocks:
|
if block_name in all_whiteblocks:
|
||||||
self.state.active_whiteblocks.append(all_whiteblocks[block_name])
|
self.state.active_whiteblocks.append(
|
||||||
|
all_whiteblocks[block_name])
|
||||||
success_wbs.append(block_name)
|
success_wbs.append(block_name)
|
||||||
else:
|
else:
|
||||||
fail_blocks.append(block_name)
|
fail_blocks.append(block_name)
|
||||||
@@ -107,7 +109,7 @@ class AntiDriftDaemon(ServiceInterface):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def schedule(self, blackblock_name: 's') -> 's':
|
def schedule(self, blackblock_name: "s") -> "s":
|
||||||
"""Schedule blackblock based if it has a non-zero timeout value."""
|
"""Schedule blackblock based if it has a non-zero timeout value."""
|
||||||
all_blackblocks = {bb.name: bb for bb in self.config.blackblocks}
|
all_blackblocks = {bb.name: bb for bb in self.config.blackblocks}
|
||||||
if blackblock_name not in all_blackblocks:
|
if blackblock_name not in all_blackblocks:
|
||||||
@@ -132,35 +134,47 @@ class AntiDriftDaemon(ServiceInterface):
|
|||||||
return m
|
return m
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def stop(self) -> 's':
|
def stop(self) -> "s":
|
||||||
self.reset_block_state()
|
self.reset_block_state()
|
||||||
m = "Blacklist only mode."
|
m = "Blacklist only mode."
|
||||||
logging.info(m)
|
logging.info(m)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def pause(self) -> 's':
|
def pause(self) -> "s":
|
||||||
self.state.pause = True
|
self.state.pause = True
|
||||||
m = "Antidrift paused."
|
m = "Antidrift paused."
|
||||||
logging.info(m)
|
logging.info(m)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def unpause(self) -> 's':
|
def unpause(self) -> "s":
|
||||||
self.state.pause = False
|
self.state.pause = False
|
||||||
m = "Antidrift unpaused."
|
m = "Antidrift unpaused."
|
||||||
logging.info(m)
|
logging.info(m)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def intention(self, intention: 's') -> 's':
|
def intention(self, intention: "s") -> "s":
|
||||||
self.state.intention = intention
|
self.state.intention = intention
|
||||||
m = f"Antidrift intention set to '{intention}'"
|
m = f"Antidrift intention set to '{intention}'."
|
||||||
logging.info(m)
|
logging.info(m)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
@method()
|
@method()
|
||||||
def status(self) -> 's':
|
def block(self, block: "s") -> "s":
|
||||||
|
# self.state.intention = intention
|
||||||
|
if self.state.active_blackblocks and \
|
||||||
|
block not in self.state.active_blackblocks[0].keywords:
|
||||||
|
self.state.active_blackblocks[0].keywords.append(block)
|
||||||
|
self.config.save()
|
||||||
|
m = f"✅Antidrift add block '{block}'."
|
||||||
|
logging.info(m)
|
||||||
|
return m
|
||||||
|
return f"⚠️ '{block}' not added."
|
||||||
|
|
||||||
|
@method()
|
||||||
|
def status(self) -> "s":
|
||||||
white_active = bool(self.state.active_whiteblocks)
|
white_active = bool(self.state.active_whiteblocks)
|
||||||
black_active = bool(self.state.active_blackblocks)
|
black_active = bool(self.state.active_blackblocks)
|
||||||
m = "🟢 ad "
|
m = "🟢 ad "
|
||||||
@@ -172,7 +186,8 @@ class AntiDriftDaemon(ServiceInterface):
|
|||||||
match (white_active, black_active):
|
match (white_active, black_active):
|
||||||
case (True, _):
|
case (True, _):
|
||||||
m += "wb: "
|
m += "wb: "
|
||||||
m += " ".join(map(lambda b: b.name, self.state.active_whiteblocks))
|
m += " ".join(map(lambda b: b.name,
|
||||||
|
self.state.active_whiteblocks))
|
||||||
if inactive_bbs:
|
if inactive_bbs:
|
||||||
m += " "
|
m += " "
|
||||||
m += inactive_bbs
|
m += inactive_bbs
|
||||||
@@ -197,24 +212,28 @@ class AntiDriftDaemon(ServiceInterface):
|
|||||||
|
|
||||||
def log_window(self):
|
def log_window(self):
|
||||||
window = XWindow()
|
window = XWindow()
|
||||||
utc_timestamp = datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
utc_timestamp = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
# Remove time strings and numbers with decimal from window.name
|
# Remove time strings and numbers with decimal from window.name
|
||||||
window_name = re.sub(r'\b\d\d:\d\d\b', '', window.name)
|
window_name = re.sub(r"\b\d\d:\d\d\b", "", window.name)
|
||||||
window_name = re.sub(r'\b\d+.\d\d\b', '', window_name)
|
window_name = re.sub(r"\b\d+.\d\d\b", "", window_name)
|
||||||
window_name = re.sub(r'[+-]\d+.\d+%', '', window_name)
|
window_name = re.sub(r"[+-]\d+.\d+%", "", window_name)
|
||||||
|
|
||||||
intention = self.state.intention
|
intention = self.state.intention
|
||||||
log_line = [utc_timestamp, window_name, window.cls, intention]
|
log_line = [utc_timestamp, window_name, window.cls, intention]
|
||||||
with self.config.window_log_file.open('a', newline='') as f:
|
with self.config.window_log_file.open("a", newline="") as f:
|
||||||
writer = csv.writer(f)
|
writer = csv.writer(f)
|
||||||
writer.writerow(log_line)
|
writer.writerow(log_line)
|
||||||
|
|
||||||
async def enforce_pause(self):
|
async def enforce_pause(self):
|
||||||
xwindow.notify("Goint to minimize window because of pause...")
|
if XWindow().is_active() is False:
|
||||||
|
return
|
||||||
|
xwindow.notify("⚠️ No active windows during pause!")
|
||||||
for _ in range(8):
|
for _ in range(8):
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
if not XWindow().is_active() or self.state.pause is False:
|
window = XWindow()
|
||||||
|
if not window.is_active() or self.state.pause is False:
|
||||||
|
xwindow.notify("✅ Thanks.")
|
||||||
return
|
return
|
||||||
|
|
||||||
window = XWindow()
|
window = XWindow()
|
||||||
@@ -228,12 +247,12 @@ class AntiDriftDaemon(ServiceInterface):
|
|||||||
delay = int(self.config.enforce_delay_ms / 1000)
|
delay = int(self.config.enforce_delay_ms / 1000)
|
||||||
for i in range(delay, 0, -1):
|
for i in range(delay, 0, -1):
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
xwindow.notify(f"AntiDrift will minimize in {i}s.")
|
xwindow.notify(f"⚠️ AntiDrift will minimize in {i}s.")
|
||||||
if not window_is_blocked(self.state, silent=True):
|
if not window_is_blocked(self.state, silent=True):
|
||||||
xwindow.notify("We are gucci again.")
|
xwindow.notify("✅ We are gucci again.")
|
||||||
return
|
return
|
||||||
window = XWindow()
|
window = XWindow()
|
||||||
xwindow.notify(f"Minimize {window.name[:30]}.")
|
xwindow.notify(f"⛔ Minimize {window.name[:30]}.")
|
||||||
window.minimize()
|
window.minimize()
|
||||||
|
|
||||||
|
|
||||||
@@ -266,8 +285,7 @@ def window_is_blocked(state: State, silent: bool = False) -> bool:
|
|||||||
for k in b.keywords:
|
for k in b.keywords:
|
||||||
if keyword_matches_window(k, window) and b.kill:
|
if keyword_matches_window(k, window) and b.kill:
|
||||||
window.kill()
|
window.kill()
|
||||||
xwindow.notify(f"Kill for {k} on {b.name}.")
|
xwindow.notify(f"⛔ Kill for {k} on {b.name}.")
|
||||||
logging.warning(f"Kill for [red]{k}[/red] on [red]{b.name}[/red].")
|
|
||||||
return True
|
return True
|
||||||
elif keyword_matches_window(k, window):
|
elif keyword_matches_window(k, window):
|
||||||
if not silent:
|
if not silent:
|
||||||
@@ -279,7 +297,7 @@ def window_is_blocked(state: State, silent: bool = False) -> bool:
|
|||||||
return True
|
return True
|
||||||
if not whiteblocks:
|
if not whiteblocks:
|
||||||
if not silent:
|
if not silent:
|
||||||
logging.debug("All non-blackblock windows are allowed.")
|
logging.debug("ℹ️ All non-blackblock windows are allowed.")
|
||||||
return False
|
return False
|
||||||
for w in whiteblocks:
|
for w in whiteblocks:
|
||||||
for k in w.keywords:
|
for k in w.keywords:
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ def evaluate(config: Config):
|
|||||||
log_file = config.window_log_file
|
log_file = config.window_log_file
|
||||||
datapoints: List[Datapoint] = []
|
datapoints: List[Datapoint] = []
|
||||||
|
|
||||||
with open(log_file, 'r') as file:
|
with open(log_file, "r") as file:
|
||||||
reader = csv.reader(file)
|
reader = csv.reader(file)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
timestamp_str, title, tool, intention = row
|
timestamp_str, title, tool, intention = row
|
||||||
if title != '':
|
if title != "":
|
||||||
timestamp = datetime.fromisoformat(timestamp_str)
|
timestamp = datetime.fromisoformat(timestamp_str)
|
||||||
datapoint = Datapoint(timestamp, title, tool, intention)
|
datapoint = Datapoint(timestamp, title, tool, intention)
|
||||||
datapoints.append(datapoint)
|
datapoints.append(datapoint)
|
||||||
@@ -53,7 +53,7 @@ def evaluate(config: Config):
|
|||||||
prev_datapoint = None
|
prev_datapoint = None
|
||||||
prev_evaluation = None
|
prev_evaluation = None
|
||||||
for d in datapoints:
|
for d in datapoints:
|
||||||
if d.title == '':
|
if d.title == "":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Get evaluation of current datapoint
|
# Get evaluation of current datapoint
|
||||||
@@ -79,7 +79,7 @@ def evaluate(config: Config):
|
|||||||
def parse_result(result: str) -> Optional[Evaluation]:
|
def parse_result(result: str) -> Optional[Evaluation]:
|
||||||
try:
|
try:
|
||||||
content = json.loads(result.strip())
|
content = json.loads(result.strip())
|
||||||
return Evaluation(content['level'], content['reason'])
|
return Evaluation(content["level"], content["reason"])
|
||||||
except (ValueError, KeyError):
|
except (ValueError, KeyError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ def evaluate_datapoint(title, tool, intention) -> Optional[str]:
|
|||||||
if r.status_code == 200:
|
if r.status_code == 200:
|
||||||
response = r.json()
|
response = r.json()
|
||||||
message_response = response["choices"][0]["message"]
|
message_response = response["choices"][0]["message"]
|
||||||
return message_response['content']
|
return message_response["content"]
|
||||||
else:
|
else:
|
||||||
xwindow.notify(f"Antidrift - GPT - Response error status code {r.status_code}")
|
xwindow.notify(f"Antidrift - GPT - Response error status code {r.status_code}")
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
def is_window_blocked(window_name: str, blocked: List[re.Pattern]) -> bool:
|
|
||||||
for b in blocked:
|
|
||||||
if b.findall(window_name):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def kill_sequence(blocked: List[re.Pattern]) -> None:
|
|
||||||
def to_display(name: str) -> str:
|
|
||||||
return name if len(name) < 30 else name[:30] + "..."
|
|
||||||
|
|
||||||
for count in range(5, 0, -1):
|
|
||||||
window_name, window_pid = get_active_window_name_and_pid()
|
|
||||||
if not is_window_blocked(window_name, blocked):
|
|
||||||
notify(f"[okay] {to_display(window_name)}")
|
|
||||||
return
|
|
||||||
notify(f"[kill {count}s] {to_display(window_name)}")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
p = psutil.Process(int(window_pid))
|
|
||||||
p.terminate()
|
|
||||||
|
|
||||||
|
|
||||||
def notify(message: str) -> None:
|
|
||||||
"""Notify user via the Xorg notify-send command."""
|
|
||||||
env = {**os.environ, "DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus"}
|
|
||||||
user = env.get("SUDO_USER", None)
|
|
||||||
if user is None:
|
|
||||||
cmd = ["notify-send", message]
|
|
||||||
else:
|
|
||||||
cmd = ["runuser", "-m", "-u", user, "notify-send", message]
|
|
||||||
subprocess.run(cmd, env=env)
|
|
||||||
|
|
||||||
|
|
||||||
def write_pid_file(config: Config) -> str:
|
|
||||||
p = psutil.Process()
|
|
||||||
pid_file = os.path.join(config.directory, f"{p.pid}.pid")
|
|
||||||
with open(pid_file, "w") as f:
|
|
||||||
f.write(str(p.pid))
|
|
||||||
return pid_file
|
|
||||||
|
|
||||||
|
|
||||||
def terminate_existing_blocker(config: Config) -> None:
|
|
||||||
this_pid = psutil.Process().pid
|
|
||||||
pid_files = [f for f in config.directory if f.endswith(".pid")]
|
|
||||||
for pid_file in pid_files:
|
|
||||||
pid = int(pid_file.replace(".pid", ""))
|
|
||||||
if this_pid == pid:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
p = psutil.Process(pid)
|
|
||||||
p.terminate()
|
|
||||||
except psutil.NoSuchProcess:
|
|
||||||
print(f"Could not terminate {p.pid=}.")
|
|
||||||
pid_file = os.path.join(config.directory, pid_file)
|
|
||||||
try:
|
|
||||||
os.remove(pid_file)
|
|
||||||
except PermissionError:
|
|
||||||
print(f"Could not remove {pid_file=}.")
|
|
||||||
|
|
||||||
|
|
||||||
def load_blocked_patterns(config: Config) -> List[re.Pattern]:
|
|
||||||
blocked = []
|
|
||||||
for block in config.blocks:
|
|
||||||
for item in block.items:
|
|
||||||
s = "".join([block.prefix, item, block.postfix])
|
|
||||||
r = re.compile(s, re.IGNORECASE)
|
|
||||||
blocked.append(r)
|
|
||||||
return blocked
|
|
||||||
|
|
||||||
|
|
||||||
def init(config: Config) -> None:
|
|
||||||
terminate_existing_blocker(config)
|
|
||||||
write_pid_file(config)
|
|
||||||
|
|
||||||
|
|
||||||
def run_checks(config: Config) -> None:
|
|
||||||
# shutil.which("xdotool")
|
|
||||||
# That's what we do if anything goes wrong:
|
|
||||||
# xkill --all
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def kill_window_if_blocked(blocked: List[re.Pattern]) -> None:
|
|
||||||
window_name, window_pid = get_active_window_name_and_pid()
|
|
||||||
if is_window_blocked(window_name, blocked):
|
|
||||||
kill_sequence(blocked)
|
|
||||||
|
|
||||||
|
|
||||||
def is_process_active(process_name: str) -> bool:
|
|
||||||
for proc in psutil.process_iter():
|
|
||||||
if proc.name() == process_name:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def enforce_aw_commit():
|
|
||||||
def aw_commit_active():
|
|
||||||
return is_process_active("aw-commit")
|
|
||||||
|
|
||||||
def to_display(name: str) -> str:
|
|
||||||
return name if len(name) < 30 else name[:30] + "..."
|
|
||||||
|
|
||||||
if aw_commit_active():
|
|
||||||
return
|
|
||||||
|
|
||||||
for _ in range(10, 0, -1):
|
|
||||||
notify(f"[warning] aw-commit not running")
|
|
||||||
time.sleep(1)
|
|
||||||
if aw_commit_active():
|
|
||||||
return
|
|
||||||
|
|
||||||
if aw_commit_active():
|
|
||||||
return
|
|
||||||
|
|
||||||
window_name, window_pid = get_active_window_name_and_pid()
|
|
||||||
if window_name:
|
|
||||||
notify(f"[kill aw-commit not running] {to_display(window_name)}")
|
|
||||||
p = psutil.Process(int(window_pid))
|
|
||||||
p.terminate()
|
|
||||||
return enforce_aw_commit()
|
|
||||||
|
|
||||||
|
|
||||||
def load_window_names(config: Config) -> Set[str]:
|
|
||||||
window_names_file = os.path.join(config.directory, config.window_names)
|
|
||||||
if not os.path.isfile(window_names_file):
|
|
||||||
return set()
|
|
||||||
with open(window_names_file, "r") as f:
|
|
||||||
return {l.strip() for l in f.readlines()}
|
|
||||||
|
|
||||||
|
|
||||||
def write_window_names(config: Config, window_names: Set[str]) -> None:
|
|
||||||
window_names_file = os.path.join(config.directory, config.window_names)
|
|
||||||
window_names = "\n".join(sorted(list(window_names)))
|
|
||||||
with open(window_names_file, "w") as f:
|
|
||||||
f.write(window_names)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
"""Run main_root as root except while debugging."""
|
|
||||||
config_path = "~/.config/aw-focus/config.json"
|
|
||||||
config = Config.load_config(config_path)
|
|
||||||
|
|
||||||
if config.start_as_user:
|
|
||||||
terminate_existing_blocker(config)
|
|
||||||
main_root(config)
|
|
||||||
|
|
||||||
if os.geteuid() == 0:
|
|
||||||
newpid = os.fork()
|
|
||||||
if newpid == 0:
|
|
||||||
main_root(config)
|
|
||||||
else:
|
|
||||||
cmd = ["sudo", config.aw_focus_cmd] + sys.argv[1:]
|
|
||||||
subprocess.Popen(cmd)
|
|
||||||
|
|
||||||
|
|
||||||
def main_root(config: Config) -> None:
|
|
||||||
init(config)
|
|
||||||
blocked = load_blocked_patterns(config)
|
|
||||||
while True:
|
|
||||||
time.sleep(config.sleep_time)
|
|
||||||
run_checks(config)
|
|
||||||
kill_window_if_blocked(blocked)
|
|
||||||
if config.enforce_aw_commit:
|
|
||||||
enforce_aw_commit()
|
|
||||||
@@ -19,7 +19,9 @@ class XWindow:
|
|||||||
self.keywords = list(re.findall(r"\w+", self.name.lower()))
|
self.keywords = list(re.findall(r"\w+", self.name.lower()))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<XWindow '{self.name[:20]}' '{self.cls[:20]}'>"
|
return (
|
||||||
|
f"<XWindow '{self.name[:20]}' '{self.cls[:20]}' active: {self.is_active()}>"
|
||||||
|
)
|
||||||
|
|
||||||
def _run(self, cmd) -> str:
|
def _run(self, cmd) -> str:
|
||||||
cmd = ["xdotool"] + cmd
|
cmd = ["xdotool"] + cmd
|
||||||
@@ -37,8 +39,10 @@ class XWindow:
|
|||||||
def kill(self):
|
def kill(self):
|
||||||
self._run(["windowkill", self.window])
|
self._run(["windowkill", self.window])
|
||||||
|
|
||||||
def is_active(self):
|
def is_active(self) -> bool:
|
||||||
return True if self.name else False
|
current_desktop = self._run(["get_desktop"])
|
||||||
|
window_desktop = self._run(["get_desktop_for_window", self.window])
|
||||||
|
return True if self.name and current_desktop == window_desktop else False
|
||||||
|
|
||||||
|
|
||||||
def notify(message: str) -> None:
|
def notify(message: str) -> None:
|
||||||
|
|||||||
29
main.py
29
main.py
@@ -2,11 +2,9 @@ import logging
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import signal
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import argparse
|
import argparse
|
||||||
import psutil
|
import psutil
|
||||||
import rich
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from rich.logging import RichHandler
|
from rich.logging import RichHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -15,20 +13,22 @@ import antidrift.client
|
|||||||
from antidrift.daemon import AntiDriftDaemon
|
from antidrift.daemon import AntiDriftDaemon
|
||||||
from antidrift.config import Config
|
from antidrift.config import Config
|
||||||
|
|
||||||
from dbus.mainloop.glib import DBusGMainLoop
|
|
||||||
|
|
||||||
DBusGMainLoop(set_as_default=True)
|
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
||||||
|
|
||||||
|
|
||||||
def get_args():
|
def get_args():
|
||||||
parser = argparse.ArgumentParser(description="AntiDrift CLI.")
|
parser = argparse.ArgumentParser(description="AntiDrift CLI.")
|
||||||
parser.add_argument("--daemon", action="store_true", help="run daemon")
|
parser.add_argument("--daemon", action="store_true", help="run daemon")
|
||||||
parser.add_argument("--evaluate", action="store_true", help="evaluate day")
|
parser.add_argument("--evaluate", action="store_true", help="evaluate day")
|
||||||
parser.add_argument("--intention", metavar="intention", help="set intention", default=None, type=str)
|
parser.add_argument(
|
||||||
|
"--intention", metavar="intention", help="set intention", default=None, type=str
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--block", metavar="block", help="add to block", default=None, type=str
|
||||||
|
)
|
||||||
parser.add_argument("--pause", action="store_true", help="pause antidrift")
|
parser.add_argument("--pause", action="store_true", help="pause antidrift")
|
||||||
parser.add_argument("--schedule", metavar="blackblock", help="schedule blackblock")
|
parser.add_argument("--schedule", metavar="blackblock", help="schedule blackblock")
|
||||||
parser.add_argument("--start", metavar="whiteblock", nargs="+", help="start whiteblocks")
|
parser.add_argument(
|
||||||
|
"--start", metavar="whiteblock", nargs="+", help="start whiteblocks"
|
||||||
|
)
|
||||||
parser.add_argument("--status", action="store_true", help="get status from daemon")
|
parser.add_argument("--status", action="store_true", help="get status from daemon")
|
||||||
parser.add_argument("--stop", action="store_true", help="stop session")
|
parser.add_argument("--stop", action="store_true", help="stop session")
|
||||||
parser.add_argument("--tailf", action="store_true", help="tail -f log file")
|
parser.add_argument("--tailf", action="store_true", help="tail -f log file")
|
||||||
@@ -82,10 +82,13 @@ def check_for_xdotool():
|
|||||||
|
|
||||||
def kill_existing_antidrift():
|
def kill_existing_antidrift():
|
||||||
current_pid = os.getpid()
|
current_pid = os.getpid()
|
||||||
for proc in psutil.process_iter(['pid', 'name', 'exe']):
|
for proc in psutil.process_iter(["pid", "name", "exe"]):
|
||||||
if proc.info['name'] == '/usr/bin/antidrift' or proc.info['exe'] == '/usr/bin/antidrift':
|
if (
|
||||||
if proc.info['pid'] == current_pid:
|
proc.info["name"] == "/usr/bin/antidrift"
|
||||||
continue # don't kill ourselves
|
or proc.info["exe"] == "/usr/bin/antidrift"
|
||||||
|
):
|
||||||
|
if proc.info["pid"] == current_pid:
|
||||||
|
continue # don't this process
|
||||||
try:
|
try:
|
||||||
proc.kill()
|
proc.kill()
|
||||||
except psutil.AccessDenied:
|
except psutil.AccessDenied:
|
||||||
|
|||||||
Reference in New Issue
Block a user