Implement scheduling of blackbox sessions.

This commit is contained in:
2022-07-24 11:21:14 -04:00
parent f255fcffee
commit 9965ef273f
4 changed files with 82 additions and 35 deletions

View File

@@ -42,9 +42,11 @@ def tailf(config):
def client_mode(config: Config): def client_mode(config: Config):
parser = argparse.ArgumentParser(description='AntiDrift CLI.') parser = argparse.ArgumentParser(description='AntiDrift CLI.')
parser.add_argument('--start', metavar='whiteblock', nargs='+', parser.add_argument('--start', metavar='whiteblock', nargs='+',
help='start session with whiteblocks') help='start whiteblocks')
parser.add_argument('--stop', action='store_true', parser.add_argument('--stop', action='store_true',
help='stop session') help='stop session')
parser.add_argument('--schedule', metavar='blackblock',
help='schedule blackblock')
parser.add_argument('--status', action='store_true', parser.add_argument('--status', action='store_true',
help='get status from daemon') help='get status from daemon')
parser.add_argument('--tailf', action='store_true', parser.add_argument('--tailf', action='store_true',
@@ -55,6 +57,8 @@ def client_mode(config: Config):
reply = interface.start(args.start) reply = interface.start(args.start)
elif args.stop: elif args.stop:
reply = interface.stop() reply = interface.stop()
elif args.schedule:
reply = interface.schedule(args.schedule)
elif args.tailf: elif args.tailf:
tailf(config) tailf(config)
elif args.status: elif args.status:

View File

@@ -9,13 +9,12 @@ class Block(BaseModel):
name: str name: str
keywords: List[str] keywords: List[str]
kill: bool = False kill: bool = False
delay: int = 0
class Config(BaseModel): class Config(BaseModel):
blackblocks: List[Block] blackblocks: List[Block]
whiteblocks: List[Block] whiteblocks: List[Block]
active_blackblocks: List[Block] = []
active_whiteblocks: List[Block] = []
daemon_log_file: Path = Path() daemon_log_file: Path = Path()
client_log_file: Path = Path() client_log_file: Path = Path()
config_file: Path = Path() config_file: Path = Path()
@@ -33,3 +32,9 @@ class Config(BaseModel):
config = cls(**config_dict) config = cls(**config_dict)
config.config_file = Path(config_file) config.config_file = Path(config_file)
return config return config
class State(BaseModel):
active_blackblocks: List[Block] = []
active_whiteblocks: List[Block] = []
inactive_blackblocks: List[Block] = []

View File

@@ -3,7 +3,7 @@ import os
import sys import sys
import pwd import pwd
import antidrift.xwindow as xwindow import antidrift.xwindow as xwindow
from antidrift.config import Config from antidrift.config import Config, State, Block
from gi.repository import GLib, Gio from gi.repository import GLib, Gio
from typing import List from typing import List
import dbus import dbus
@@ -24,8 +24,8 @@ def reload_callback(m, f, o, event):
class AntiDriftDaemon(dbus.service.Object): class AntiDriftDaemon(dbus.service.Object):
def __init__(self, config: Config): def __init__(self, config: Config):
user_name = os.environ["SUDO_USER"] user_name = os.environ.get("SUDO_USER", pwd.getpwuid(os.getuid()).pw_name)
user_uid = pwd.getpwnam(user_name)[2] user_uid = pwd.getpwnam(user_name).pw_uid
euid = os.geteuid() euid = os.geteuid()
os.seteuid(user_uid) os.seteuid(user_uid)
bus = dbus.bus.BusConnection(f"unix:path=/run/user/{user_uid}/bus") bus = dbus.bus.BusConnection(f"unix:path=/run/user/{user_uid}/bus")
@@ -35,60 +35,102 @@ class AntiDriftDaemon(dbus.service.Object):
bus_name = dbus.service.BusName(BUS_NAME, bus=bus) bus_name = dbus.service.BusName(BUS_NAME, bus=bus)
dbus.service.Object.__init__(self, bus_name, OPATH) dbus.service.Object.__init__(self, bus_name, OPATH)
self.config = config self.config = config
self.config.active_whiteblocks = [] self.reset_block_state()
self.config.active_blackblocks = self.config.blackblocks
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)
def reset_block_state(self):
self.state = State(
active_blackblocks=self.config.blackblocks,
active_whiteblocks=[],
inactive_blackblocks=[])
@dbus.service.method(dbus_interface=IFACE, @dbus.service.method(dbus_interface=IFACE,
in_signature="as", out_signature="s") in_signature="as", out_signature="s")
def start(self, whiteblocks: List[str]) -> str: def start(self, whiteblocks: List[str]) -> str:
self.config.active_whiteblocks = [] 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 = [] success_wbs, fail_blocks = [], []
fail_wbs = [] for block_name in whiteblocks:
for wb_name in whiteblocks: if block_name in all_whiteblocks:
if wb_name in all_whiteblocks: self.state.active_whiteblocks.append(all_whiteblocks[block_name])
self.config.active_whiteblocks.append(all_whiteblocks[wb_name]) success_wbs.append(block_name)
success_wbs.append(wb_name)
else: else:
fail_wbs.append(wb_name) fail_blocks.append(block_name)
if success_wbs: if success_wbs:
wbs = ', '.join(success_wbs) wbs = ', '.join(success_wbs)
r = f"Start whiteblocks [sky_blue3]{wbs}[/sky_blue3]." r = f"Start whiteblocks [sky_blue3]{wbs}[/sky_blue3]."
logging.info(r) logging.info(r)
else: else:
r = "No whiteblocks started." r = "No whiteblocks started."
if fail_wbs: if fail_blocks:
m = f"No blackblocks [red]{', '.join(fail_wbs)}[/red]." m = f"No whiteblocks [red3]{', '.join(fail_blocks)}[/red3]."
logging.warning(m) logging.warning(m)
return r return r
@dbus.service.method(dbus_interface=IFACE,
in_signature="s", out_signature="s")
def schedule(self, blackblock_name: str) -> str:
""" Schedule blackblock based if it has a non-zero timeout value. """
all_blackblocks = {bb.name: bb for bb in self.config.blackblocks}
if blackblock_name not in all_blackblocks:
m = f"No blackblock [red3]{blackblock_name}[/red3]."
logging.warning(m)
return m
blackblock = all_blackblocks[blackblock_name]
if blackblock.delay == 0:
m = f"Blackblock [red3]{blackblock_name}[/red3] cannot be scheduled without delay."
logging.warning(m)
return m
def allow():
self.allow_blackblock(blackblock)
delay_ms = blackblock.delay * 1000 * 60
GLib.timeout_add(delay_ms, allow)
m = f"Scheduled [sky_blue3]{blackblock_name}[/sky_blue3] in {blackblock.delay} minutes."
logging.info(m)
return m
@dbus.service.method(dbus_interface=IFACE, @dbus.service.method(dbus_interface=IFACE,
in_signature="", out_signature="s") in_signature="", out_signature="s")
def stop(self) -> str: def stop(self) -> str:
self.config.active_whiteblocks = [] self.reset_block_state()
self.config.active_blackblocks = self.config.blackblocks m = 'Blacklist only mode.'
m = '[red]Stop[/red] all whitelists. Blacklist only mode.'
logging.info(m) logging.info(m)
return m return m
@dbus.service.method(dbus_interface=IFACE, @dbus.service.method(dbus_interface=IFACE,
in_signature="", out_signature="s") in_signature="", out_signature="s")
def status(self) -> str: def status(self) -> str:
white_active = bool(self.config.active_whiteblocks) white_active = bool(self.state.active_whiteblocks)
black_active = bool(self.config.active_blackblocks) black_active = bool(self.state.active_blackblocks)
m = 'ad ' m = 'ad '
inactive_bbs = ' '.join(map(lambda b: "-" + b.name, self.state.inactive_blackblocks))
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.config.active_whiteblocks)) m += ' '.join(map(lambda b: b.name, self.state.active_whiteblocks))
if inactive_bbs:
m += ' '
m += inactive_bbs
case (False, True): case (False, True):
m += 'bb' m += 'bb'
if inactive_bbs:
m += ': '
m += inactive_bbs
case _: case _:
m = 'inactive' m = 'inactive'
return m return m
def allow_blackblock(self, blackblock: Block):
if blackblock in self.state.active_blackblocks:
self.state.active_blackblocks.remove(blackblock)
if blackblock not in self.state.inactive_blackblocks:
self.state.inactive_blackblocks.append(blackblock)
m = f"Blackblock [sky_blue3]{blackblock.name}[/sky_blue3] is now allowed."
logging.info(m)
def run(self): def run(self):
def _enforce(): def _enforce():
self.enforce() self.enforce()
@@ -110,29 +152,25 @@ class AntiDriftDaemon(dbus.service.Object):
mainloop.run() mainloop.run()
def enforce(self): def enforce(self):
config = self.config
# logging.debug(f"{self.enforce_count=} {self.enforce_value=}")
if self.enforce_count >= self.enforce_value: if self.enforce_count >= self.enforce_value:
window = xwindow.XWindow() window = xwindow.XWindow()
xwindow.notify(f"Minimize {window.name[:30]}.") xwindow.notify(f"Minimize {window.name[:30]}.")
window.minimize() window.minimize()
self.enforce_count = 0 self.enforce_count = 0
elif self.enforce_count > 0 and window_is_blocked(config, True): elif self.enforce_count > 0 and window_is_blocked(self.state, True):
self.enforce_count += 1 self.enforce_count += 1
elif self.enforce_count == 0 and window_is_blocked(config): elif self.enforce_count == 0 and window_is_blocked(self.state):
self.enforce_count += 1 self.enforce_count += 1
delay = int(config.enforce_delay_ms / 1000) delay = int(self.config.enforce_delay_ms / 1000)
xwindow.notify(f"AntiDrift will minimize in {delay}s.") xwindow.notify(f"AntiDrift will minimize in {delay}s.")
elif self.enforce_count > 0: elif self.enforce_count > 0:
xwindow.notify("We are gucci again.") xwindow.notify("We are gucci again.")
self.enforce_count = 0 self.enforce_count = 0
def window_is_blocked(config: Config, silent: bool = False) -> bool: def window_is_blocked(state: State, silent: bool = False) -> bool:
# These should be selectable in the future (not all at the same time) blackblocks = state.active_blackblocks
blackblocks = config.active_blackblocks whiteblocks = state.active_whiteblocks
whiteblocks = config.active_whiteblocks
window = xwindow.XWindow() window = xwindow.XWindow()
if not window.keywords: if not window.keywords:

View File

@@ -32,7 +32,7 @@ class XWindow:
self._run(["windowquit", self.window]) self._run(["windowquit", self.window])
def kill(self): def kill(self):
self._run(["windowkill", self.window]) self._run(["windowclose", self.window])
def notify(message: str) -> None: def notify(message: str) -> None: