diversion: implement pressed and released action on Key condition (#1189)
- Track `key_up` key in addition to `key_down` - Support `pressed` or `released` action in `Key` condition - Add radio button to KeyUI to represent `pressed` or `released`
This commit is contained in:
parent
6290c84efd
commit
85a86ec3c5
|
@ -58,7 +58,9 @@ can only be `Shift`, `Control`, `Alt`, and `Super`.
|
|||
Modifiers conditions are true if their argument is the current keyboard
|
||||
modifiers.
|
||||
`Key` conditions are true if the Logitech name of the last diverted key or button down is their
|
||||
string argument. Logitech key and button names are shown in the `Key/Button Diversion`
|
||||
string argument. Alternatively, if the argument is a list `[name, action]` where `action`
|
||||
is either `'pressed'` or `'released'`, the key down or key up events of `name` argument are
|
||||
matched, respectively. Logitech key and button names are shown in the `Key/Button Diversion`
|
||||
setting. Some keyboards have Gn keys, which are diverted using the 'Divert G Keys' setting.
|
||||
`Test` conditions are true if their test evaluates to true on the feature,
|
||||
report, and data of the current notification.
|
||||
|
|
|
@ -154,6 +154,7 @@ if x11:
|
|||
# See docs/rules.md for documentation
|
||||
|
||||
key_down = None
|
||||
key_up = None
|
||||
|
||||
|
||||
def signed(bytes):
|
||||
|
@ -406,21 +407,50 @@ class Modifiers(Condition):
|
|||
|
||||
|
||||
class Key(Condition):
|
||||
def __init__(self, key):
|
||||
DOWN = 'pressed'
|
||||
UP = 'released'
|
||||
|
||||
def __init__(self, args):
|
||||
default_key = 0
|
||||
default_action = self.DOWN
|
||||
|
||||
key, action = None, None
|
||||
|
||||
if not args or not isinstance(args, (list, str)):
|
||||
_log.warn('rule Key arguments unknown: %s' % args)
|
||||
key = default_key
|
||||
action = default_action
|
||||
elif isinstance(args, str):
|
||||
_log.debug('rule Key assuming action "%s" for "%s"' % (default_action, args))
|
||||
key = args
|
||||
action = default_action
|
||||
elif isinstance(args, list):
|
||||
if len(args) == 1:
|
||||
_log.debug('rule Key assuming action "%s" for "%s"' % (default_action, args))
|
||||
key, action = args[0], default_action
|
||||
elif len(args) >= 2:
|
||||
key, action = args[:2]
|
||||
|
||||
if isinstance(key, str) and key in _CONTROL:
|
||||
self.key = _CONTROL[key]
|
||||
else:
|
||||
_log.warn('rule Key argument not name of a Logitech key: %s', key)
|
||||
self.key = 0
|
||||
_log.warn('rule Key key name not name of a Logitech key: %s' % key)
|
||||
self.key = default_key
|
||||
|
||||
if isinstance(action, str) and action in (self.DOWN, self.UP):
|
||||
self.action = action
|
||||
else:
|
||||
_log.warn('rule Key action unknown: %s, assuming %s' % (action, default_action))
|
||||
self.action = default_action
|
||||
|
||||
def __str__(self):
|
||||
return 'Key: ' + (str(self.key) if self.key else 'None')
|
||||
return 'Key: %s (%s)' % ((str(self.key) if self.key else 'None'), self.action)
|
||||
|
||||
def evaluate(self, feature, notification, device, status, last_result):
|
||||
return self.key and self.key == key_down
|
||||
return bool(self.key and self.key == (key_down if self.action == self.DOWN else key_up))
|
||||
|
||||
def data(self):
|
||||
return {'Key': str(self.key)}
|
||||
return {'Key': [str(self.key), self.action]}
|
||||
|
||||
|
||||
def bit_test(start, end, bits):
|
||||
|
@ -728,28 +758,36 @@ if x11:
|
|||
])
|
||||
|
||||
keys_down = []
|
||||
g_keys_down = 0x00
|
||||
g_keys_down = 0x00000000
|
||||
|
||||
|
||||
# process a notification
|
||||
def process_notification(device, status, notification, feature):
|
||||
if not x11:
|
||||
return
|
||||
global keys_down, g_keys_down, key_down
|
||||
key_down = None
|
||||
global keys_down, g_keys_down, key_down, key_up
|
||||
key_down, key_up = None, None
|
||||
# need to keep track of keys that are down to find a new key down
|
||||
if feature == _F.REPROG_CONTROLS_V4 and notification.address == 0x00:
|
||||
new_keys_down = _unpack('!4H', notification.data[:8])
|
||||
for key in new_keys_down:
|
||||
if key and key not in keys_down:
|
||||
key_down = key
|
||||
for key in keys_down:
|
||||
if key and key not in new_keys_down:
|
||||
key_up = key
|
||||
keys_down = new_keys_down
|
||||
# and also G keys down
|
||||
elif feature == _F.GKEY and notification.address == 0x00:
|
||||
new_g_keys_down, = _unpack('!B', notification.data[:1])
|
||||
new_g_keys_down = _unpack('!4B', notification.data[:4])
|
||||
# process 32 bits, byte by byte
|
||||
for byte_idx in range(4):
|
||||
new_byte, old_byte = new_g_keys_down[byte_idx], g_keys_down[byte_idx]
|
||||
for i in range(1, 9):
|
||||
if new_g_keys_down & (0x01 << (i - 1)) and not g_keys_down & (0x01 << (i - 1)):
|
||||
key_down = _CONTROL['G' + str(i)]
|
||||
if new_byte & (0x01 << (i - 1)) and not old_byte & (0x01 << (i - 1)):
|
||||
key_down = _CONTROL['G' + str(i + 8 * byte_idx)]
|
||||
if old_byte & (0x01 << (i - 1)) and not new_byte & (0x01 << (i - 1)):
|
||||
key_up = _CONTROL['G' + str(i + 8 * byte_idx)]
|
||||
g_keys_down = new_g_keys_down
|
||||
rules.evaluate(feature, notification, device, status, True)
|
||||
|
||||
|
|
|
@ -272,7 +272,7 @@ CONTROL = _NamedInts(
|
|||
LED_Toggle=0x013B, #
|
||||
)
|
||||
|
||||
for i in range(1, 7): # add in G keys - these are not really Logitech Controls
|
||||
for i in range(1, 33): # add in G keys - these are not really Logitech Controls
|
||||
CONTROL[0x1000 + i] = 'G' + str(i)
|
||||
|
||||
CONTROL._fallback = lambda x: 'unknown:%04X' % x
|
||||
|
|
|
@ -27,6 +27,7 @@ from shlex import quote as shlex_quote
|
|||
from gi.repository import Gdk, GObject, Gtk
|
||||
from logitech_receiver import diversion as _DIV
|
||||
from logitech_receiver.diversion import XK_KEYS as _XK_KEYS
|
||||
from logitech_receiver.diversion import Key as _Key
|
||||
from logitech_receiver.diversion import buttons as _buttons
|
||||
from logitech_receiver.hidpp20 import FEATURE as _ALL_FEATURES
|
||||
from logitech_receiver.special_keys import CONTROL as _CONTROL
|
||||
|
@ -1000,25 +1001,36 @@ class KeyUI(ConditionUI):
|
|||
|
||||
def create_widgets(self):
|
||||
self.widgets = {}
|
||||
self.field = CompletionEntry(
|
||||
self.key_field = CompletionEntry(
|
||||
self.KEY_NAMES, 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)
|
||||
self.key_field.set_size_request(600, 0)
|
||||
self.key_field.connect('changed', self._on_update)
|
||||
self.widgets[self.key_field] = (0, 0, 2, 1)
|
||||
self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(None, 'Key down')
|
||||
self.action_pressed_radio.connect('toggled', self._on_update, _Key.DOWN)
|
||||
self.widgets[self.action_pressed_radio] = (2, 0, 1, 1)
|
||||
self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, 'Key up')
|
||||
self.action_released_radio.connect('toggled', self._on_update, _Key.UP)
|
||||
self.widgets[self.action_released_radio] = (3, 0, 1, 1)
|
||||
|
||||
def show(self, component):
|
||||
super().show(component)
|
||||
with self.ignore_changes():
|
||||
self.field.set_text(str(component.key) if self.component.key else '')
|
||||
self.key_field.set_text(str(component.key) if self.component.key else '')
|
||||
if not component.action or component.action == _Key.DOWN:
|
||||
self.action_pressed_radio.set_active(True)
|
||||
else:
|
||||
self.action_released_radio.set_active(True)
|
||||
|
||||
def collect_value(self):
|
||||
return self.field.get_text()
|
||||
action = _Key.UP if self.action_released_radio.get_active() else _Key.DOWN
|
||||
return [self.key_field.get_text(), action]
|
||||
|
||||
def _on_update(self, *args):
|
||||
super()._on_update(*args)
|
||||
icon = 'dialog-warning' if not self.component.key else ''
|
||||
self.field.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
|
||||
icon = 'dialog-warning' if not self.component.key or not self.component.action else ''
|
||||
self.key_field.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
|
||||
|
||||
@classmethod
|
||||
def left_label(cls, component):
|
||||
|
@ -1026,7 +1038,7 @@ class KeyUI(ConditionUI):
|
|||
|
||||
@classmethod
|
||||
def right_label(cls, component):
|
||||
return '%s (%04X)' % (str(component.key), int(component.key)) if component.key else 'None'
|
||||
return '%s (%04X) (%s)' % (str(component.key), int(component.key), component.action) if component.key else 'None'
|
||||
|
||||
|
||||
class TestUI(ConditionUI):
|
||||
|
|
Loading…
Reference in New Issue