Merge pull request #339 from hasB4K/pullrequest-mouse_pos
core: Change command_endpoint and on_click for supporting i3bar mouse positions
This commit is contained in:
commit
8400d1a53a
@ -39,6 +39,7 @@ Kenneth Lyons
|
|||||||
krypt-n
|
krypt-n
|
||||||
Lukáš Mandák
|
Lukáš Mandák
|
||||||
Łukasz Jędrzejewski
|
Łukasz Jędrzejewski
|
||||||
|
Mathis Felardos
|
||||||
Matthias Pronk
|
Matthias Pronk
|
||||||
Matthieu Coudron
|
Matthieu Coudron
|
||||||
Matus Telgarsky
|
Matus Telgarsky
|
||||||
|
@ -29,9 +29,19 @@ class CommandEndpoint:
|
|||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
def _command_endpoint(self):
|
def _command_endpoint(self):
|
||||||
for command in self.io_handler_factory().read():
|
for cmd in self.io_handler_factory().read():
|
||||||
target_module = self.modules.get(command["instance"])
|
target_module = self.modules.get(cmd["instance"])
|
||||||
if target_module and target_module.on_click(command["button"]):
|
|
||||||
|
button = cmd["button"]
|
||||||
|
kwargs = {"button_id": button}
|
||||||
|
try:
|
||||||
|
kwargs.update({"pos_x": cmd["x"],
|
||||||
|
"pos_y": cmd["y"]})
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if target_module:
|
||||||
|
target_module.on_click(button, **kwargs)
|
||||||
target_module.run()
|
target_module.run()
|
||||||
self.io.async_refresh()
|
self.io.async_refresh()
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import inspect
|
import inspect
|
||||||
|
import traceback
|
||||||
|
|
||||||
from i3pystatus.core.settings import SettingsBase
|
from i3pystatus.core.settings import SettingsBase
|
||||||
from i3pystatus.core.threading import Manager
|
from i3pystatus.core.threading import Manager
|
||||||
from i3pystatus.core.util import (convert_position,
|
from i3pystatus.core.util import (convert_position,
|
||||||
MultiClickHandler)
|
MultiClickHandler)
|
||||||
from i3pystatus.core.command import execute
|
from i3pystatus.core.command import execute
|
||||||
from i3pystatus.core.command import run_through_shell
|
|
||||||
|
|
||||||
|
|
||||||
def is_method_of(method, object):
|
def is_method_of(method, object):
|
||||||
@ -26,26 +26,36 @@ class Module(SettingsBase):
|
|||||||
|
|
||||||
settings = (
|
settings = (
|
||||||
('on_leftclick', "Callback called on left click (see :ref:`callbacks`)"),
|
('on_leftclick', "Callback called on left click (see :ref:`callbacks`)"),
|
||||||
|
('on_middleclick', "Callback called on middle click (see :ref:`callbacks`)"),
|
||||||
('on_rightclick', "Callback called on right click (see :ref:`callbacks`)"),
|
('on_rightclick', "Callback called on right click (see :ref:`callbacks`)"),
|
||||||
('on_upscroll', "Callback called on scrolling up (see :ref:`callbacks`)"),
|
('on_upscroll', "Callback called on scrolling up (see :ref:`callbacks`)"),
|
||||||
('on_downscroll', "Callback called on scrolling down (see :ref:`callbacks`)"),
|
('on_downscroll', "Callback called on scrolling down (see :ref:`callbacks`)"),
|
||||||
('on_doubleleftclick', "Callback called on double left click (see :ref:`callbacks`)"),
|
('on_doubleleftclick', "Callback called on double left click (see :ref:`callbacks`)"),
|
||||||
|
('on_doubleleftclick', "Callback called on double left click (see :ref:`callbacks`)"),
|
||||||
|
('on_doublemiddleclick', "Callback called on double middle click (see :ref:`callbacks`)"),
|
||||||
('on_doublerightclick', "Callback called on double right click (see :ref:`callbacks`)"),
|
('on_doublerightclick', "Callback called on double right click (see :ref:`callbacks`)"),
|
||||||
('on_doubleupscroll', "Callback called on double scroll up (see :ref:`callbacks`)"),
|
('on_doubleupscroll', "Callback called on double scroll up (see :ref:`callbacks`)"),
|
||||||
('on_doubledownscroll', "Callback called on double scroll down (see :ref:`callbacks`)"),
|
('on_doubledownscroll', "Callback called on double scroll down (see :ref:`callbacks`)"),
|
||||||
|
('on_otherclick', "Callback called on other click (see :ref:`callbacks`)"),
|
||||||
|
('on_doubleotherclick', "Callback called on double other click (see :ref:`callbacks`)"),
|
||||||
('multi_click_timeout', "Time (in seconds) before a single click is executed."),
|
('multi_click_timeout', "Time (in seconds) before a single click is executed."),
|
||||||
('hints', "Additional output blocks for module output (see :ref:`hints`)"),
|
('hints', "Additional output blocks for module output (see :ref:`hints`)"),
|
||||||
)
|
)
|
||||||
|
|
||||||
on_leftclick = None
|
on_leftclick = None
|
||||||
|
on_middleclick = None
|
||||||
on_rightclick = None
|
on_rightclick = None
|
||||||
on_upscroll = None
|
on_upscroll = None
|
||||||
on_downscroll = None
|
on_downscroll = None
|
||||||
on_doubleleftclick = None
|
on_doubleleftclick = None
|
||||||
|
on_doublemiddleclick = None
|
||||||
on_doublerightclick = None
|
on_doublerightclick = None
|
||||||
on_doubleupscroll = None
|
on_doubleupscroll = None
|
||||||
on_doubledownscroll = None
|
on_doubledownscroll = None
|
||||||
|
|
||||||
|
on_otherclick = None
|
||||||
|
on_doubleotherclick = None
|
||||||
|
|
||||||
multi_click_timeout = 0.25
|
multi_click_timeout = 0.25
|
||||||
|
|
||||||
hints = {"markup": "none"}
|
hints = {"markup": "none"}
|
||||||
@ -78,15 +88,37 @@ class Module(SettingsBase):
|
|||||||
def run(self):
|
def run(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __log_button_event(self, button, cb, args, action):
|
def __log_button_event(self, button, cb, args, action, **kwargs):
|
||||||
msg = "{}: button={}, cb='{}', args={}, type='{}'".format(
|
msg = "{}: button={}, cb='{}', args={}, kwargs={}, type='{}'".format(
|
||||||
self.__name__, button, cb, args, action)
|
self.__name__, button, cb, args, kwargs, action)
|
||||||
self.logger.debug(msg)
|
self.logger.debug(msg)
|
||||||
|
|
||||||
def __button_callback_handler(self, button, cb):
|
def __button_callback_handler(self, button, cb, **kwargs):
|
||||||
|
|
||||||
|
def call_callback(cb, *args, **kwargs):
|
||||||
|
# Recover the function if wrapped (with get_module for example)
|
||||||
|
wrapped_cb = getattr(cb, "__wrapped__", None)
|
||||||
|
if wrapped_cb:
|
||||||
|
locals()["self"] = self # Add self to the local stack frame
|
||||||
|
tmp_cb = wrapped_cb
|
||||||
|
else:
|
||||||
|
tmp_cb = cb
|
||||||
|
|
||||||
|
try:
|
||||||
|
args_spec = inspect.getargspec(tmp_cb)
|
||||||
|
except Exception:
|
||||||
|
args_spec = inspect.ArgSpec([], None, None, None)
|
||||||
|
|
||||||
|
# Remove all variables present in kwargs that are not used in the
|
||||||
|
# callback, except if there is a keyword argument.
|
||||||
|
if not args_spec.keywords:
|
||||||
|
kwargs = {k: v for k, v in kwargs.items()
|
||||||
|
if k in args_spec.args}
|
||||||
|
cb(*args, **kwargs)
|
||||||
|
|
||||||
if not cb:
|
if not cb:
|
||||||
self.__log_button_event(button, None, None,
|
self.__log_button_event(button, None, None,
|
||||||
"No callback attached")
|
"No callback attached", **kwargs)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if isinstance(cb, list):
|
if isinstance(cb, list):
|
||||||
@ -97,25 +129,35 @@ class Module(SettingsBase):
|
|||||||
try:
|
try:
|
||||||
our_method = is_method_of(cb, self)
|
our_method = is_method_of(cb, self)
|
||||||
if callable(cb) and not our_method:
|
if callable(cb) and not our_method:
|
||||||
self.__log_button_event(button, cb, args, "Python callback")
|
self.__log_button_event(button, cb, args,
|
||||||
cb(*args)
|
"Python callback", **kwargs)
|
||||||
|
call_callback(cb, *args, **kwargs)
|
||||||
elif our_method:
|
elif our_method:
|
||||||
cb(self, *args)
|
self.__log_button_event(button, cb, args,
|
||||||
|
"Method callback", **kwargs)
|
||||||
|
call_callback(cb, self, *args, **kwargs)
|
||||||
elif hasattr(self, cb):
|
elif hasattr(self, cb):
|
||||||
if cb is not "run":
|
if cb is not "run":
|
||||||
# CommandEndpoint already calls run() after every
|
# CommandEndpoint already calls run() after every
|
||||||
# callback to instantly update any changed state due
|
# callback to instantly update any changed state due
|
||||||
# to the callback's actions.
|
# to the callback's actions.
|
||||||
self.__log_button_event(button, cb, args, "Member callback")
|
self.__log_button_event(button, cb, args,
|
||||||
getattr(self, cb)(*args)
|
"Member callback", **kwargs)
|
||||||
|
call_callback(getattr(self, cb), *args, **kwargs)
|
||||||
else:
|
else:
|
||||||
self.__log_button_event(button, cb, args, "External command")
|
self.__log_button_event(button, cb, args,
|
||||||
|
"External command", **kwargs)
|
||||||
|
|
||||||
if hasattr(self, "data"):
|
if hasattr(self, "data"):
|
||||||
args = [arg.format(**self.data) for arg in args]
|
kwargs.update(self.data)
|
||||||
cb = cb.format(**self.data)
|
|
||||||
|
args = [str(arg).format(**kwargs) for arg in args]
|
||||||
|
cb = cb.format(**kwargs)
|
||||||
execute(cb + " " + " ".join(args), detach=True)
|
execute(cb + " " + " ".join(args), detach=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.critical("Exception while processing button callback: {!r}".format(e))
|
self.logger.critical("Exception while processing button "
|
||||||
|
"callback: {!r}".format(e))
|
||||||
|
self.logger.critical(traceback.format_exc())
|
||||||
|
|
||||||
# Notify status handler
|
# Notify status handler
|
||||||
try:
|
try:
|
||||||
@ -123,20 +165,22 @@ class Module(SettingsBase):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_click(self, button):
|
def on_click(self, button, **kwargs):
|
||||||
"""
|
"""
|
||||||
Maps a click event with its associated callback.
|
Maps a click event with its associated callback.
|
||||||
|
|
||||||
Currently implemented events are:
|
Currently implemented events are:
|
||||||
|
|
||||||
=========== ================ =========
|
============ ================ =========
|
||||||
Event Callback setting Button ID
|
Event Callback setting Button ID
|
||||||
=========== ================ =========
|
============ ================ =========
|
||||||
Left click on_leftclick 1
|
Left click on_leftclick 1
|
||||||
Right click on_rightclick 3
|
Middle click on_middleclick 2
|
||||||
Scroll up on_upscroll 4
|
Right click on_rightclick 3
|
||||||
Scroll down on_downscroll 5
|
Scroll up on_upscroll 4
|
||||||
=========== ================ =========
|
Scroll down on_downscroll 5
|
||||||
|
Others on_otherclick > 5
|
||||||
|
============ ================ =========
|
||||||
|
|
||||||
The action is determined by the nature (type and value) of the callback
|
The action is determined by the nature (type and value) of the callback
|
||||||
setting in the following order:
|
setting in the following order:
|
||||||
@ -144,8 +188,8 @@ class Module(SettingsBase):
|
|||||||
1. If null callback (``None``), no action is taken.
|
1. If null callback (``None``), no action is taken.
|
||||||
2. If it's a `python function`, call it and pass any additional
|
2. If it's a `python function`, call it and pass any additional
|
||||||
arguments.
|
arguments.
|
||||||
3. If it's name of a `member method` of current module (string), call it
|
3. If it's name of a `member method` of current module (string), call
|
||||||
and pass any additional arguments.
|
it and pass any additional arguments.
|
||||||
4. If the name does not match with `member method` name execute program
|
4. If the name does not match with `member method` name execute program
|
||||||
with such name.
|
with such name.
|
||||||
|
|
||||||
@ -153,24 +197,19 @@ class Module(SettingsBase):
|
|||||||
callback settings and examples.
|
callback settings and examples.
|
||||||
|
|
||||||
:param button: The ID of button event received from i3bar.
|
:param button: The ID of button event received from i3bar.
|
||||||
:type button: int
|
:param kwargs: Further information received from i3bar like the
|
||||||
|
positions of the mouse where the click occured.
|
||||||
:return: Returns ``True`` if a valid callback action was executed.
|
:return: Returns ``True`` if a valid callback action was executed.
|
||||||
``False`` otherwise.
|
``False`` otherwise.
|
||||||
:rtype: bool
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if button == 1: # Left mouse button
|
actions = ['leftclick', 'middleclick', 'rightclick',
|
||||||
action = 'leftclick'
|
'upscroll', 'downscroll']
|
||||||
elif button == 3: # Right mouse button
|
try:
|
||||||
action = 'rightclick'
|
action = actions[button - 1]
|
||||||
elif button == 4: # mouse wheel up
|
except (TypeError, IndexError):
|
||||||
action = 'upscroll'
|
self.__log_button_event(button, None, None, "Other button")
|
||||||
elif button == 5: # mouse wheel down
|
action = "otherclick"
|
||||||
action = 'downscroll'
|
|
||||||
else:
|
|
||||||
self.__log_button_event(button, None, None, "Unhandled button")
|
|
||||||
return False
|
|
||||||
|
|
||||||
m_click = self.__multi_click
|
m_click = self.__multi_click
|
||||||
|
|
||||||
@ -184,15 +223,13 @@ class Module(SettingsBase):
|
|||||||
# Get callback function
|
# Get callback function
|
||||||
cb = getattr(self, 'on_%s' % action, None)
|
cb = getattr(self, 'on_%s' % action, None)
|
||||||
|
|
||||||
has_double_handler = getattr(self, 'on_%s' % double_action, None) is not None
|
double_handler = getattr(self, 'on_%s' % double_action, None)
|
||||||
delay_execution = (not double and has_double_handler)
|
delay_execution = (not double and double_handler)
|
||||||
|
|
||||||
if delay_execution:
|
if delay_execution:
|
||||||
m_click.set_timer(button, cb)
|
m_click.set_timer(button, cb, **kwargs)
|
||||||
else:
|
else:
|
||||||
self.__button_callback_handler(button, cb)
|
self.__button_callback_handler(button, cb, **kwargs)
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def move(self, position):
|
def move(self, position):
|
||||||
self.position = position
|
self.position = position
|
||||||
|
@ -514,8 +514,9 @@ class MultiClickHandler(object):
|
|||||||
self.timer = None
|
self.timer = None
|
||||||
self.button = None
|
self.button = None
|
||||||
self.cb = None
|
self.cb = None
|
||||||
|
self.kwargs = None
|
||||||
|
|
||||||
def set_timer(self, button, cb):
|
def set_timer(self, button, cb, **kwargs):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.clear_timer()
|
self.clear_timer()
|
||||||
|
|
||||||
@ -524,6 +525,7 @@ class MultiClickHandler(object):
|
|||||||
args=[self._timer_id])
|
args=[self._timer_id])
|
||||||
self.button = button
|
self.button = button
|
||||||
self.cb = cb
|
self.cb = cb
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
|
|
||||||
@ -544,7 +546,7 @@ class MultiClickHandler(object):
|
|||||||
with self.lock:
|
with self.lock:
|
||||||
if self._timer_id != timer_id:
|
if self._timer_id != timer_id:
|
||||||
return
|
return
|
||||||
self.callback_handler(self.button, self.cb)
|
self.callback_handler(self.button, self.cb, **self.kwargs)
|
||||||
self.clear_timer()
|
self.clear_timer()
|
||||||
|
|
||||||
def check_double(self, button):
|
def check_double(self, button):
|
||||||
@ -553,7 +555,7 @@ class MultiClickHandler(object):
|
|||||||
|
|
||||||
ret = True
|
ret = True
|
||||||
if button != self.button:
|
if button != self.button:
|
||||||
self.callback_handler(self.button, self.cb)
|
self.callback_handler(self.button, self.cb, **self.kwargs)
|
||||||
ret = False
|
ret = False
|
||||||
|
|
||||||
self.clear_timer()
|
self.clear_timer()
|
||||||
|
Loading…
Reference in New Issue
Block a user