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
|
||||
as rules.
|
||||
|
||||
`Process` conditions are true if the name of the active process starts with
|
||||
their string argument.
|
||||
`Process` conditions are true if the process for focus input window
|
||||
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
|
||||
notification is their string argument.
|
||||
`Report` conditions are if true if the report number in the current
|
||||
|
|
|
@ -52,58 +52,14 @@ try:
|
|||
XK_KEYS = vars(_XK)
|
||||
disp_prog = Display()
|
||||
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:
|
||||
_log.warn('X11 not available - rules will not be activated', exc_info=_sys.exc_info())
|
||||
XK_KEYS = {}
|
||||
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
|
||||
# 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]}
|
||||
|
||||
|
||||
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):
|
||||
def __init__(self, process):
|
||||
self.process = process
|
||||
|
@ -340,13 +322,33 @@ class Process(Condition):
|
|||
def evaluate(self, feature, notification, device, status, last_result):
|
||||
if not isinstance(self.process, str):
|
||||
return False
|
||||
return bool(active_process_name and active_process_name.startswith(self.process)) or \
|
||||
bool(active_wm_class_name and active_wm_class_name.startswith(self.process))
|
||||
result = any(bool(s and s.startswith(self.process)) for s in x11_focus_prog())
|
||||
return result
|
||||
|
||||
def data(self):
|
||||
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):
|
||||
def __init__(self, feature):
|
||||
if not (isinstance(feature, str) and feature in _F):
|
||||
|
@ -715,6 +717,7 @@ COMPONENTS = {
|
|||
'Or': Or,
|
||||
'And': And,
|
||||
'Process': Process,
|
||||
'MouseProcess': MouseProcess,
|
||||
'Feature': Feature,
|
||||
'Report': Report,
|
||||
'Modifiers': Modifiers,
|
||||
|
|
|
@ -508,6 +508,7 @@ class DiversionDialog:
|
|||
[
|
||||
(_('Feature'), _DIV.Feature, FeatureUI.FEATURES_WITH_DIVERSION[0]),
|
||||
(_('Process'), _DIV.Process, ''),
|
||||
(_('MouseProcess'), _DIV.MouseProcess, ''),
|
||||
(_('Report'), _DIV.Report, 0),
|
||||
(_('Modifiers'), _DIV.Modifiers, []),
|
||||
(_('Key'), _DIV.Key, ''),
|
||||
|
@ -877,6 +878,34 @@ class ProcessUI(ConditionUI):
|
|||
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 = _DIV.Feature
|
||||
|
@ -1433,6 +1462,7 @@ COMPONENT_UI = {
|
|||
_DIV.Or: OrUI,
|
||||
_DIV.And: AndUI,
|
||||
_DIV.Process: ProcessUI,
|
||||
_DIV.MouseProcess: MouseProcessUI,
|
||||
_DIV.Feature: FeatureUI,
|
||||
_DIV.Report: ReportUI,
|
||||
_DIV.Modifiers: ModifiersUI,
|
||||
|
|
Loading…
Reference in New Issue