Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions bin/input-remapper-gtk
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ from inputremapper.daemon import DaemonProxy
from inputremapper.logger import logger, update_verbosity, log_info


def start_processes() -> DaemonProxy:
def start_processes(ignore_pkexec_errors=False) -> DaemonProxy:
"""Start reader-service and daemon via pkexec to run in the background."""
# this function is overwritten in tests
try:
ReaderService.pkexec_reader_service()
ReaderService.pkexec_reader_service(ingore_errors=ignore_pkexec_errors)
except Exception as e:
logger.error(e)
sys.exit(11)
Expand All @@ -57,7 +57,12 @@ if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument(
'-d', '--debug', action='store_true', dest='debug',
help=_('Displays additional debug information'),
help=_('displays additional debug information'),
default=False
)
parser.add_argument(
'-R', '--no-root', action='store_true', dest='no_root',
help=_('allow rejecting root access (by cancelling the pkexec dialog)'),
default=False
)

Expand Down Expand Up @@ -85,8 +90,8 @@ if __name__ == '__main__':

# create the reader before we start the reader-service (start_processes) otherwise
# it can come to race conditions with the creation of pipes
reader_client = ReaderClient(message_broker, _Groups())
daemon = start_processes()
reader_client = ReaderClient(message_broker, _Groups(), ignore_pkexec_errors=options.no_root)
daemon = start_processes(ignore_pkexec_errors=options.no_root)

data_manager = DataManager(
message_broker, GlobalConfig(), reader_client, daemon, GlobalUInputs(), system_mapping
Expand Down
115 changes: 87 additions & 28 deletions data/input-remapper.glade
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
<property name="margin-end">2</property>
<property name="icon-name">media-playback-start</property>
</object>
<object class="GtkImage" id="clear-icon">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">sweeper</property>
</object>
<object class="GtkImage" id="copy-icon1">
<property name="visible">True</property>
<property name="can-focus">False</property>
Expand Down Expand Up @@ -436,6 +441,7 @@ Shortcut: ctrl + del</property>
<object class="GtkEntry" id="preset_name_input">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="placeholder-text" translatable="yes">enter preset name...</property>
</object>
<packing>
<property name="expand">True</property>
Expand Down Expand Up @@ -501,6 +507,7 @@ Shortcut: ctrl + del</property>
<property name="can-focus">False</property>
<property name="margin-top">18</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
Expand All @@ -521,36 +528,51 @@ Shortcut: ctrl + del</property>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="margin-bottom">18</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="spacing">6</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel" id="combination-label">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">no input configured</property>
<property name="wrap">True</property>
<property name="halign">center</property>
<property name="margin-bottom">18</property>
<child>
<object class="GtkLabel" id="combination-label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">no input configured</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="recording_status">
<property name="can-focus">False</property>
<property name="opacity">0.5</property>
<property name="label" translatable="yes"> (recording ...)</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="recording_status">
<property name="can-focus">False</property>
<property name="opacity">0.5</property>
<property name="label" translatable="yes"> (recording ...)</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
Expand All @@ -564,11 +586,9 @@ Shortcut: ctrl + del</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="margin-start">18</property>
<property name="margin-end">18</property>
<property name="margin-bottom">18</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="spacing">6</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="create_mapping_button">
<property name="label" translatable="yes">Add</property>
Expand All @@ -580,8 +600,8 @@ Shortcut: ctrl + del</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
Expand All @@ -596,7 +616,7 @@ Shortcut: ctrl + del</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
Expand All @@ -610,7 +630,7 @@ Shortcut: ctrl + del</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
Expand All @@ -629,6 +649,45 @@ Shortcut: ctrl + del</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkBox" id="mapping-filter-box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkEntry" id="mapping-filter-input">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="placeholder-text" translatable="yes">filter mappings...</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="mapping-filter-clear-button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Clear search filter</property>
<property name="halign">start</property>
<property name="image">clear-icon</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
Expand Down
120 changes: 119 additions & 1 deletion inputremapper/gui/components/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@

from gi.repository import Gtk

from typing import Optional
from typing import (
Optional,
Iterator,
)

from inputremapper.configs.mapping import MappingData

Expand Down Expand Up @@ -173,3 +176,118 @@ def _render(self):
label.append(self._mapping_name or "?")

self._gui.set_label(" / ".join(label))


class ListFilterControl:
"""Implements UI-side filtering of list widgets.

The following example creates a new ``ListFilterControl`` for a given
``Gtk.ListBox`` and a given ``Gtk.Entry`` for text input. It also sets all
optional arguments to override some default behavior.

>>> ListFilterControl(
>>> my_gtk_listbox,
>>> my_gtk_entry,
>>> clear_button=my_gtk_button, # use an optional clear button
>>> case_sensitive=True, # change default behavior
>>> get_row_name=MyRow.get_name # custom row name getter
>>> )

"""

MAX_WIDGET_TREE_TEXT_SEARCH_DEPTH = 10

def __init__(
self,
# message_broker: MessageBroker,
controlled_listbox: Gtk.ListBox,
filter_entry: Gtk.GtkEntry,
clear_button: Gtk.Button = None,
case_sensitive=False,
get_row_name=None,
):
self._controlled_listbox: Gtk.ListBox = controlled_listbox
self._filter_entry: Gtk.Entry = filter_entry
self._clear_button: Gtk.Button = clear_button

self._filter_value: str = ""
self._case_sensitive: bool = bool(case_sensitive)
self._get_row_name = get_row_name or self.get_row_name

self._connect_gtk_signals()

@classmethod
def get_row_name(T, row: Gtk.ListBoxRow) -> str:
"""
Returns the visible text of a Gtk.ListBoxRow from both the row's `name`
attribute or the row's text in the UI.
"""
text = getattr(row, "name", "")

# find and join all text in the ListBoxRow
text += " ".join(v for v in T.get_widget_tree_text(row) if v != "")

return text.strip()

@classmethod
def get_widget_tree_text(T, widget: Gtk.Widget, level=0) -> Iterator[str]:
"""
Recursively traverses the tree of child widgets starting from the given
widget, and yields the text of all text-containing widgets.
"""
if level > T.MAX_WIDGET_TREE_TEXT_SEARCH_DEPTH:
return

if hasattr(widget, "get_label"):
yield (widget.get_label() or "").strip()
if hasattr(widget, "get_text"):
yield (widget.get_text() or "").strip()
if isinstance(widget, Gtk.Container):
for t in widget.get_children():
yield from T.get_widget_tree_text(t, level=level + 1)

def _connect_gtk_signals(self):
if self._clear_button:
self._clear_button.connect("clicked", self.on_gtk_clear_button_clicked)
self._filter_entry.connect("key-release-event", self.on_gtk_filter_entry_input)

# apply defined filter by sending out the corresponding events
def apply_filter(self):
self._apply_filter_to_listbox_children()

# matches the current filter_value and filter_options with the given value
def match_filter(self, value: str):
value = (value or "").strip()

# if filter is not set, all rows need to match
if self._filter_value == "":
return True

if self._case_sensitive:
return self._filter_value in value
else:
return self._filter_value.lower() in value.lower()

def _apply_filter_to_listbox_children(self):
value = self._filter_value.lower()
selected: Gtk.ListBoxRow = None
row: Gtk.ListBoxRow = None
for row in self._controlled_listbox.get_children():
if self.match_filter(self._get_row_name(row)):
# show matching rows, then select the first row
row.show()
if selected is None:
selected = row
self._controlled_listbox.select_row(selected)
else:
# hide non-matching rows
row.hide()

def on_gtk_filter_entry_input(self, _, event: Gdk.EventKey):
self._filter_value = (self._filter_entry.get_text() or "").strip()
self.apply_filter()

def on_gtk_clear_button_clicked(self, *_):
self._filter_entry.set_text("")
self._filter_value = ""
self.apply_filter()
7 changes: 5 additions & 2 deletions inputremapper/gui/reader_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ class ReaderClient:
# how long to wait for the reader-service at most
_timeout: int = 5

def __init__(self, message_broker: MessageBroker, groups: _Groups):
def __init__(
self, message_broker: MessageBroker, groups: _Groups, ignore_pkexec_errors=False
):
self.groups = groups
self.message_broker = message_broker

Expand All @@ -86,13 +88,14 @@ def __init__(self, message_broker: MessageBroker, groups: _Groups):
self.attach_to_events()

self._read_timeout = GLib.timeout_add(30, self._read)
self.ignore_pkexec_errors = ignore_pkexec_errors

def ensure_reader_service_running(self):
if ReaderService.is_running():
return

logger.info("ReaderService not running anymore, restarting")
ReaderService.pkexec_reader_service()
ReaderService.pkexec_reader_service(ingore_errors=self.ignore_pkexec_errors)

# wait until the ReaderService is up

Expand Down
Loading