ui: add GUI for diversion rules (draft)

This commit is contained in:
Vinícius 2020-11-14 00:34:01 -03:00 committed by Peter F. Patel-Schneider
parent f42ff35f24
commit 1379da70a8
3 changed files with 1368 additions and 5 deletions

View File

@ -60,7 +60,7 @@ def active_program():
window = disp_prog.create_resource_object('window', window_id)
window_pid = window.get_full_property(NET_WM_PID, 0).value[0]
return psutil.Process(window_pid).name()
except Xlib.error.XError: # simplify dealing with BadWindow
except (Xlib.error.XError, AttributeError): # simplify dealing with BadWindow
return None
@ -187,6 +187,9 @@ class Rule(RuleComponent):
return result
return result
def data(self):
return {'Rule': [c.data() for c in self.components]}
class Condition(RuleComponent):
def __init__(self, *args):
@ -201,6 +204,8 @@ class Condition(RuleComponent):
class Not(Condition):
def __init__(self, op):
if isinstance(op, list) and len(op) == 1:
op = op[0]
self.op = op
self.component = self.compile(op)
@ -211,6 +216,9 @@ class Not(Condition):
result = self.component.evaluate(feature, notification, device, status, last_result)
return None if result is None else not result
def data(self):
return {'Not': self.component.data()}
class Or(Condition):
def __init__(self, args):
@ -229,6 +237,9 @@ class Or(Condition):
return result
return result
def data(self):
return {'Or': [c.data() for c in self.components]}
class And(Condition):
def __init__(self, args):
@ -247,12 +258,16 @@ class And(Condition):
return result
return result
def data(self):
return {'And': [c.data() for c in self.components]}
class Process(Condition):
def __init__(self, process):
self.process = process
if not isinstance(process, str):
_log.warn('rule Process argument not a string: %s', process)
self.process = str(process)
def __str__(self):
return 'Process: ' + str(self.process)
@ -260,6 +275,9 @@ class Process(Condition):
def evaluate(self, feature, notification, device, status, last_result):
return active_process_name.startswith(self.process) if isinstance(self.process, str) else False
def data(self):
return {'Process': str(self.process)}
class Feature(Condition):
def __init__(self, feature):
@ -274,6 +292,9 @@ class Feature(Condition):
def evaluate(self, feature, notification, device, status, last_result):
return feature == self.feature
def data(self):
return {'Feature': str(self.feature)}
class Report(Condition):
def __init__(self, report):
@ -288,6 +309,9 @@ class Report(Condition):
def evaluate(self, report, notification, device, status, last_result):
return (notification.address >> 4) == self.report
def data(self):
return {'Report': self.report}
MODIFIERS = {'Shift': 0x01, 'Control': 0x04, 'Alt': 0x08, 'Super': 0x40}
MODIFIER_MASK = MODIFIERS['Shift'] + MODIFIERS['Control'] + MODIFIERS['Alt'] + MODIFIERS['Super']
@ -297,9 +321,11 @@ class Modifiers(Condition):
def __init__(self, modifiers):
modifiers = [modifiers] if isinstance(modifiers, str) else modifiers
self.desired = 0
self.modifiers = []
for k in modifiers:
if k in MODIFIERS:
self.desired += MODIFIERS.get(k, 0)
self.modifiers.append(k)
else:
_log.warn('unknown rule Modifier value: %s', k)
@ -309,6 +335,9 @@ class Modifiers(Condition):
def evaluate(self, feature, notification, device, status, last_result):
return self.desired == (current_key_modifiers & MODIFIER_MASK)
def data(self):
return {'Modifiers': [str(m) for m in self.modifiers]}
class Key(Condition):
def __init__(self, key):
@ -324,6 +353,9 @@ class Key(Condition):
def evaluate(self, feature, notification, device, status, last_result):
return self.key and self.key == key_down
def data(self):
return {'Key': str(self.key)}
def bit_test(start, end, bits):
return lambda f, r, d: int.from_bytes(d[start:end], byteorder='big', signed=True) & bits
@ -360,6 +392,9 @@ class Test(Condition):
def evaluate(self, feature, notification, device, status, last_result):
return self.function(feature, notification.address, notification.data)
def data(self):
return {'Test': str(self.test)}
class Action(RuleComponent):
def __init__(self, *args):
@ -410,6 +445,9 @@ class KeyPress(Action):
self.keyUp(reversed(self.keys), current)
return None
def data(self):
return {'KeyPress': [str(k) for k in self.key_symbols]}
# KeyDown is dangerous as the key can auto-repeat and make your system unusable
# class KeyDown(KeyPress):
@ -427,6 +465,7 @@ class MouseScroll(Action):
amounts = amounts[0]
if not (len(amounts) == 2 and all([isinstance(a, numbers.Number) for a in amounts])):
_log.warn('rule MouseScroll argument not two numbers %s', amounts)
amounts = [0, 0]
self.amounts = amounts
def __str__(self):
@ -443,6 +482,9 @@ class MouseScroll(Action):
mouse.scroll(*amounts)
return None
def data(self):
return {'MouseScroll': self.amounts[:]}
class MouseClick(Action):
def __init__(self, args):
@ -470,6 +512,9 @@ class MouseClick(Action):
mouse.click(getattr(_mouse.Button, self.button), self.count)
return None
def data(self):
return {'MouseClick': [self.button, self.count]}
class Execute(Action):
def __init__(self, args):
@ -477,7 +522,7 @@ class Execute(Action):
args = [args]
if not (isinstance(args, list) and all(isinstance(arg), str) for arg in args):
_log.warn('rule Execute argument not list of strings: %s', args)
self.args = None
self.args = []
else:
self.args = args
@ -491,6 +536,9 @@ class Execute(Action):
subprocess.Popen(self.args)
return None
def data(self):
return {'Execute': self.args[:]}
COMPONENTS = {
'Rule': Rule,
@ -510,7 +558,7 @@ COMPONENTS = {
}
rules = Rule([
built_in_rules = Rule([
## Some malformed Rules for testing
## Rule([Process(0), Feature(0), Modifiers(['XX', 0]), Modifiers('XXX'), Modifiers([0]),
## KeyPress(['XXXXX', 0]), KeyPress(['XXXXXX']), KeyPress(0),
@ -567,9 +615,12 @@ def process_notification(device, status, notification, feature):
_XDG_CONFIG_HOME = _os.environ.get('XDG_CONFIG_HOME') or _path.expanduser(_path.join('~', '.config'))
_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'rules.yaml')
rules = built_in_rules
def _load_config_rule_file():
global rules
loaded_rules = []
if _path.isfile(_file_path):
try:
with open(_file_path, 'r') as config_file:
@ -581,10 +632,10 @@ def _load_config_rule_file():
loaded_rules.append(rule)
if _log.isEnabledFor(_INFO):
_log.info('loaded %d rules from %s', len(loaded_rules), config_file.name)
loaded_rules.extend(rules.components)
rules = Rule(loaded_rules)
except Exception as e:
_log.error('failed to load from %s\n%s', _file_path, e)
loaded_rules.append(built_in_rules)
rules = Rule(loaded_rules)
_load_config_rule_file()

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,7 @@ from . import action as _action
from . import config_panel as _config_panel
from . import icons as _icons
from .about import show_window as _show_about_window
from .diversion_rules import show_window as _show_diversion_window
_log = getLogger(__name__)
del getLogger
@ -327,6 +328,8 @@ def _create_window_layout():
_('About') + ' ' + NAME, 'help-about', icon_size=_SMALL_BUTTON_ICON_SIZE, clicked=_show_about_window
)
bottom_buttons_box.add(about_button)
diversion_button = _new_button(_('Diversion rules'), '', icon_size=_SMALL_BUTTON_ICON_SIZE, clicked=_show_diversion_window)
bottom_buttons_box.add(diversion_button)
# solaar_version = Gtk.Label()
# solaar_version.set_markup('<small>' + NAME + ' v' + VERSION + '</small>')