rules: add rule condition for process under mouse
This commit is contained in:
parent
39a5350f30
commit
cdc7a27c9e
|
@ -50,8 +50,10 @@ A Or condition is true if its last evaluated component evaluates to a true
|
||||||
value. `And` conditions take a sequence of components are evaluted the same
|
value. `And` conditions take a sequence of components are evaluted the same
|
||||||
as rules.
|
as rules.
|
||||||
|
|
||||||
`Process` conditions are true if the name of the active process starts with
|
`Process` conditions are true if the process for focus input window
|
||||||
their string argument.
|
or the window's Window manager class or instance name starts with their string argument.
|
||||||
|
`MouseProcess` conditions are true if the process for the window under the mouse
|
||||||
|
or the window's Window manager class or instance name starts with their string argument.
|
||||||
`Feature` conditions are if true if the name of the feature of the current
|
`Feature` conditions are if true if the name of the feature of the current
|
||||||
notification is their string argument.
|
notification is their string argument.
|
||||||
`Report` conditions are if true if the report number in the current
|
`Report` conditions are if true if the report number in the current
|
||||||
|
|
|
@ -52,58 +52,14 @@ try:
|
||||||
XK_KEYS = vars(_XK)
|
XK_KEYS = vars(_XK)
|
||||||
disp_prog = Display()
|
disp_prog = Display()
|
||||||
x11 = True
|
x11 = True
|
||||||
|
NET_ACTIVE_WINDOW = disp_prog.intern_atom('_NET_ACTIVE_WINDOW')
|
||||||
|
NET_WM_PID = disp_prog.intern_atom('_NET_WM_PID')
|
||||||
|
WM_CLASS = disp_prog.intern_atom('WM_CLASS')
|
||||||
except Exception:
|
except Exception:
|
||||||
_log.warn('X11 not available - rules will not be activated', exc_info=_sys.exc_info())
|
_log.warn('X11 not available - rules will not be activated', exc_info=_sys.exc_info())
|
||||||
XK_KEYS = {}
|
XK_KEYS = {}
|
||||||
x11 = False
|
x11 = False
|
||||||
|
|
||||||
if x11:
|
|
||||||
# determine name of active process
|
|
||||||
NET_ACTIVE_WINDOW = disp_prog.intern_atom('_NET_ACTIVE_WINDOW')
|
|
||||||
NET_WM_PID = disp_prog.intern_atom('_NET_WM_PID')
|
|
||||||
WM_CLASS = disp_prog.intern_atom('WM_CLASS')
|
|
||||||
root2 = disp_prog.screen().root
|
|
||||||
root2.change_attributes(event_mask=Xlib.X.PropertyChangeMask)
|
|
||||||
|
|
||||||
active_process_name = None
|
|
||||||
active_wm_class_name = None
|
|
||||||
|
|
||||||
|
|
||||||
def active_program_name():
|
|
||||||
try:
|
|
||||||
window_id = root2.get_full_property(NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType).value[0]
|
|
||||||
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, AttributeError): # simplify dealing with BadWindow
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def active_program_wm_class():
|
|
||||||
try:
|
|
||||||
window_id = root2.get_full_property(NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType).value[0]
|
|
||||||
window = disp_prog.create_resource_object('window', window_id)
|
|
||||||
window_wm_class = window.get_wm_class()[0]
|
|
||||||
return window_wm_class
|
|
||||||
except (Xlib.error.XError, AttributeError): # simplify dealing with BadWindow
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def determine_active_program_and_wm_class():
|
|
||||||
global active_process_name
|
|
||||||
global active_wm_class_name
|
|
||||||
active_process_name = active_program_name()
|
|
||||||
active_wm_class_name = active_program_wm_class()
|
|
||||||
while True:
|
|
||||||
event = disp_prog.next_event()
|
|
||||||
if event.type == Xlib.X.PropertyNotify and event.atom == NET_ACTIVE_WINDOW:
|
|
||||||
active_process_name = active_program_name()
|
|
||||||
active_wm_class_name = active_program_wm_class()
|
|
||||||
|
|
||||||
|
|
||||||
if x11:
|
|
||||||
_thread.start_new_thread(determine_active_program_and_wm_class, ())
|
|
||||||
|
|
||||||
# determine current key modifiers
|
# determine current key modifiers
|
||||||
# there must be a better way to do this
|
# there must be a better way to do this
|
||||||
|
|
||||||
|
@ -327,6 +283,32 @@ class And(Condition):
|
||||||
return {'And': [c.data() for c in self.components]}
|
return {'And': [c.data() for c in self.components]}
|
||||||
|
|
||||||
|
|
||||||
|
def x11_focus_prog():
|
||||||
|
pid = wm_class = None
|
||||||
|
window = disp_prog.get_input_focus().focus
|
||||||
|
while window:
|
||||||
|
pid = window.get_full_property(NET_WM_PID, 0)
|
||||||
|
wm_class = window.get_wm_class()
|
||||||
|
if wm_class:
|
||||||
|
break
|
||||||
|
window = window.query_tree().parent
|
||||||
|
name = psutil.Process(pid.value[0]).name() if pid else None
|
||||||
|
return (wm_class[0], wm_class[1], name) if wm_class else (name)
|
||||||
|
|
||||||
|
|
||||||
|
def x11_pointer_prog():
|
||||||
|
pid = wm_class = None
|
||||||
|
window = disp_prog.screen().root.query_pointer().child
|
||||||
|
for window in reversed(window.query_tree().children):
|
||||||
|
pid = window.get_full_property(NET_WM_PID, 0)
|
||||||
|
wm_class = window.get_wm_class()
|
||||||
|
if wm_class:
|
||||||
|
break
|
||||||
|
window = window.query_tree().parent
|
||||||
|
name = psutil.Process(pid.value[0]).name() if pid else None
|
||||||
|
return (wm_class[0], wm_class[1], name) if wm_class else (name)
|
||||||
|
|
||||||
|
|
||||||
class Process(Condition):
|
class Process(Condition):
|
||||||
def __init__(self, process):
|
def __init__(self, process):
|
||||||
self.process = process
|
self.process = process
|
||||||
|
@ -340,13 +322,33 @@ class Process(Condition):
|
||||||
def evaluate(self, feature, notification, device, status, last_result):
|
def evaluate(self, feature, notification, device, status, last_result):
|
||||||
if not isinstance(self.process, str):
|
if not isinstance(self.process, str):
|
||||||
return False
|
return False
|
||||||
return bool(active_process_name and active_process_name.startswith(self.process)) or \
|
result = any(bool(s and s.startswith(self.process)) for s in x11_focus_prog())
|
||||||
bool(active_wm_class_name and active_wm_class_name.startswith(self.process))
|
return result
|
||||||
|
|
||||||
def data(self):
|
def data(self):
|
||||||
return {'Process': str(self.process)}
|
return {'Process': str(self.process)}
|
||||||
|
|
||||||
|
|
||||||
|
class MouseProcess(Condition):
|
||||||
|
def __init__(self, process):
|
||||||
|
self.process = process
|
||||||
|
if not isinstance(process, str):
|
||||||
|
_log.warn('rule MouseProcess argument not a string: %s', process)
|
||||||
|
self.process = str(process)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'MouseProcess: ' + str(self.process)
|
||||||
|
|
||||||
|
def evaluate(self, feature, notification, device, status, last_result):
|
||||||
|
if not isinstance(self.process, str):
|
||||||
|
return False
|
||||||
|
result = any(bool(s and s.startswith(self.process)) for s in x11_pointer_prog())
|
||||||
|
return result
|
||||||
|
|
||||||
|
def data(self):
|
||||||
|
return {'MouseProcess': str(self.process)}
|
||||||
|
|
||||||
|
|
||||||
class Feature(Condition):
|
class Feature(Condition):
|
||||||
def __init__(self, feature):
|
def __init__(self, feature):
|
||||||
if not (isinstance(feature, str) and feature in _F):
|
if not (isinstance(feature, str) and feature in _F):
|
||||||
|
@ -715,6 +717,7 @@ COMPONENTS = {
|
||||||
'Or': Or,
|
'Or': Or,
|
||||||
'And': And,
|
'And': And,
|
||||||
'Process': Process,
|
'Process': Process,
|
||||||
|
'MouseProcess': MouseProcess,
|
||||||
'Feature': Feature,
|
'Feature': Feature,
|
||||||
'Report': Report,
|
'Report': Report,
|
||||||
'Modifiers': Modifiers,
|
'Modifiers': Modifiers,
|
||||||
|
|
|
@ -508,6 +508,7 @@ class DiversionDialog:
|
||||||
[
|
[
|
||||||
(_('Feature'), _DIV.Feature, FeatureUI.FEATURES_WITH_DIVERSION[0]),
|
(_('Feature'), _DIV.Feature, FeatureUI.FEATURES_WITH_DIVERSION[0]),
|
||||||
(_('Process'), _DIV.Process, ''),
|
(_('Process'), _DIV.Process, ''),
|
||||||
|
(_('MouseProcess'), _DIV.MouseProcess, ''),
|
||||||
(_('Report'), _DIV.Report, 0),
|
(_('Report'), _DIV.Report, 0),
|
||||||
(_('Modifiers'), _DIV.Modifiers, []),
|
(_('Modifiers'), _DIV.Modifiers, []),
|
||||||
(_('Key'), _DIV.Key, ''),
|
(_('Key'), _DIV.Key, ''),
|
||||||
|
@ -877,6 +878,34 @@ class ProcessUI(ConditionUI):
|
||||||
return str(component.process)
|
return str(component.process)
|
||||||
|
|
||||||
|
|
||||||
|
class MouseProcessUI(ConditionUI):
|
||||||
|
|
||||||
|
CLASS = _DIV.MouseProcess
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
self.widgets = {}
|
||||||
|
self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=True)
|
||||||
|
self.field.set_size_request(600, 0)
|
||||||
|
self.field.connect('changed', self._on_update)
|
||||||
|
self.widgets[self.field] = (0, 0, 1, 1)
|
||||||
|
|
||||||
|
def show(self, component):
|
||||||
|
super().show(component)
|
||||||
|
with self.ignore_changes():
|
||||||
|
self.field.set_text(component.process)
|
||||||
|
|
||||||
|
def collect_value(self):
|
||||||
|
return self.field.get_text()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def left_label(cls, component):
|
||||||
|
return _('MouseProcess')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def right_label(cls, component):
|
||||||
|
return str(component.process)
|
||||||
|
|
||||||
|
|
||||||
class FeatureUI(ConditionUI):
|
class FeatureUI(ConditionUI):
|
||||||
|
|
||||||
CLASS = _DIV.Feature
|
CLASS = _DIV.Feature
|
||||||
|
@ -1433,6 +1462,7 @@ COMPONENT_UI = {
|
||||||
_DIV.Or: OrUI,
|
_DIV.Or: OrUI,
|
||||||
_DIV.And: AndUI,
|
_DIV.And: AndUI,
|
||||||
_DIV.Process: ProcessUI,
|
_DIV.Process: ProcessUI,
|
||||||
|
_DIV.MouseProcess: MouseProcessUI,
|
||||||
_DIV.Feature: FeatureUI,
|
_DIV.Feature: FeatureUI,
|
||||||
_DIV.Report: ReportUI,
|
_DIV.Report: ReportUI,
|
||||||
_DIV.Modifiers: ModifiersUI,
|
_DIV.Modifiers: ModifiersUI,
|
||||||
|
|
Loading…
Reference in New Issue